|
|
|
@ -241,17 +241,15 @@ class ChannelData { |
|
|
|
|
public: |
|
|
|
|
explicit ChannelConfigHelper(ChannelData* chand) : chand_(chand) {} |
|
|
|
|
|
|
|
|
|
ApplyServiceConfigResult ApplyServiceConfig( |
|
|
|
|
ChooseServiceConfigResult ChooseServiceConfig( |
|
|
|
|
const Resolver::Result& result) override; |
|
|
|
|
|
|
|
|
|
void ApplyConfigSelector( |
|
|
|
|
bool service_config_changed, |
|
|
|
|
RefCountedPtr<ConfigSelector> config_selector) override; |
|
|
|
|
void StartUsingServiceConfigForCalls() override; |
|
|
|
|
|
|
|
|
|
void ResolverTransientFailure(grpc_error* error) override; |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
static void ProcessLbPolicy( |
|
|
|
|
static void ChooseLbPolicy( |
|
|
|
|
const Resolver::Result& resolver_result, |
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config, |
|
|
|
|
RefCountedPtr<LoadBalancingPolicy::Config>* lb_policy_config); |
|
|
|
@ -267,9 +265,12 @@ class ChannelData { |
|
|
|
|
const char* reason, |
|
|
|
|
std::unique_ptr<LoadBalancingPolicy::SubchannelPicker> picker); |
|
|
|
|
|
|
|
|
|
void UpdateServiceConfigInDataPlaneLocked( |
|
|
|
|
bool service_config_changed, |
|
|
|
|
RefCountedPtr<ConfigSelector> config_selector); |
|
|
|
|
void UpdateServiceConfigInControlPlaneLocked( |
|
|
|
|
RefCountedPtr<ServiceConfig> service_config, |
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config, |
|
|
|
|
const char* lb_policy_name, const grpc_channel_args* args); |
|
|
|
|
|
|
|
|
|
void UpdateServiceConfigInDataPlaneLocked(); |
|
|
|
|
|
|
|
|
|
void CreateResolvingLoadBalancingPolicyLocked(); |
|
|
|
|
|
|
|
|
@ -320,7 +321,6 @@ class ChannelData { |
|
|
|
|
grpc_core::UniquePtr<char> health_check_service_name_; |
|
|
|
|
RefCountedPtr<ServiceConfig> saved_service_config_; |
|
|
|
|
RefCountedPtr<ConfigSelector> saved_config_selector_; |
|
|
|
|
bool received_first_resolver_result_ = false; |
|
|
|
|
// The number of SubchannelWrapper instances referencing a given Subchannel.
|
|
|
|
|
std::map<Subchannel*, int> subchannel_refcount_map_; |
|
|
|
|
// The set of SubchannelWrappers that currently exist.
|
|
|
|
@ -1433,23 +1433,18 @@ class ChannelData::ClientChannelControlHelper |
|
|
|
|
// ChannelData::ChannelConfigHelper
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// Synchronous callback from ResolvingLoadBalancingPolicy to process a
|
|
|
|
|
// resolver result update.
|
|
|
|
|
ChannelData::ChannelConfigHelper::ApplyServiceConfigResult |
|
|
|
|
ChannelData::ChannelConfigHelper::ApplyServiceConfig( |
|
|
|
|
ChannelData::ChannelConfigHelper::ChooseServiceConfigResult |
|
|
|
|
ChannelData::ChannelConfigHelper::ChooseServiceConfig( |
|
|
|
|
const Resolver::Result& result) { |
|
|
|
|
ApplyServiceConfigResult service_config_result; |
|
|
|
|
ChooseServiceConfigResult service_config_result; |
|
|
|
|
RefCountedPtr<ServiceConfig> service_config; |
|
|
|
|
// If resolver did not return a service config or returned an invalid service
|
|
|
|
|
// config, we need a fallback service config.
|
|
|
|
|
if (result.service_config_error != GRPC_ERROR_NONE) { |
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, "chand=%p: resolver returned service config error: %s", |
|
|
|
|
chand_, grpc_error_string(result.service_config_error)); |
|
|
|
|
} |
|
|
|
|
// If the service config was invalid, then fallback to the saved service
|
|
|
|
|
// config. If there is no saved config either, use the default service
|
|
|
|
|
// config.
|
|
|
|
|
// If the service config was invalid, then fallback to the
|
|
|
|
|
// previously returned service config.
|
|
|
|
|
if (chand_->saved_service_config_ != nullptr) { |
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, |
|
|
|
@ -1458,99 +1453,51 @@ ChannelData::ChannelConfigHelper::ApplyServiceConfig( |
|
|
|
|
chand_); |
|
|
|
|
} |
|
|
|
|
service_config = chand_->saved_service_config_; |
|
|
|
|
} else if (chand_->default_service_config_ != nullptr) { |
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, |
|
|
|
|
"chand=%p: resolver returned invalid service config. Using " |
|
|
|
|
"default service config provided by client API.", |
|
|
|
|
chand_); |
|
|
|
|
} |
|
|
|
|
service_config = chand_->default_service_config_; |
|
|
|
|
} else { |
|
|
|
|
// No previously returned config, so put the channel into
|
|
|
|
|
// TRANSIENT_FAILURE.
|
|
|
|
|
service_config_result.no_valid_service_config = true; |
|
|
|
|
return service_config_result; |
|
|
|
|
} |
|
|
|
|
} else if (result.service_config == nullptr) { |
|
|
|
|
if (chand_->default_service_config_ != nullptr) { |
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, |
|
|
|
|
"chand=%p: resolver returned no service config. Using default " |
|
|
|
|
"service config provided by client API.", |
|
|
|
|
chand_); |
|
|
|
|
} |
|
|
|
|
service_config = chand_->default_service_config_; |
|
|
|
|
// Resolver did not return any service config.
|
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, |
|
|
|
|
"chand=%p: resolver returned no service config. Using default " |
|
|
|
|
"service config for channel.", |
|
|
|
|
chand_); |
|
|
|
|
} |
|
|
|
|
service_config = chand_->default_service_config_; |
|
|
|
|
} else { |
|
|
|
|
// Use service config returned by resolver.
|
|
|
|
|
service_config = result.service_config; |
|
|
|
|
} |
|
|
|
|
service_config_result.service_config_error = |
|
|
|
|
GRPC_ERROR_REF(result.service_config_error); |
|
|
|
|
if (service_config == nullptr && |
|
|
|
|
result.service_config_error != GRPC_ERROR_NONE) { |
|
|
|
|
service_config_result.no_valid_service_config = true; |
|
|
|
|
return service_config_result; |
|
|
|
|
} |
|
|
|
|
// Process service config.
|
|
|
|
|
grpc_core::UniquePtr<char> service_config_json; |
|
|
|
|
GPR_ASSERT(service_config != nullptr); |
|
|
|
|
// Extract global config for client channel.
|
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config = |
|
|
|
|
nullptr; |
|
|
|
|
if (service_config != nullptr) { |
|
|
|
|
parsed_service_config = |
|
|
|
|
static_cast<const internal::ClientChannelGlobalParsedConfig*>( |
|
|
|
|
service_config->GetGlobalParsedConfig( |
|
|
|
|
internal::ClientChannelServiceConfigParser::ParserIndex())); |
|
|
|
|
} |
|
|
|
|
static_cast<const internal::ClientChannelGlobalParsedConfig*>( |
|
|
|
|
service_config->GetGlobalParsedConfig( |
|
|
|
|
internal::ClientChannelServiceConfigParser::ParserIndex())); |
|
|
|
|
// Find LB policy config.
|
|
|
|
|
ChooseLbPolicy(result, parsed_service_config, |
|
|
|
|
&service_config_result.lb_policy_config); |
|
|
|
|
// Check if the config has changed.
|
|
|
|
|
service_config_result.service_config_changed = |
|
|
|
|
((service_config == nullptr) != |
|
|
|
|
(chand_->saved_service_config_ == nullptr)) || |
|
|
|
|
(service_config != nullptr && |
|
|
|
|
service_config->json_string() != |
|
|
|
|
chand_->saved_service_config_->json_string()); |
|
|
|
|
chand_->saved_service_config_ == nullptr || |
|
|
|
|
service_config->json_string() != |
|
|
|
|
chand_->saved_service_config_->json_string(); |
|
|
|
|
// If it has, apply the global parameters now.
|
|
|
|
|
if (service_config_result.service_config_changed) { |
|
|
|
|
service_config_json.reset(gpr_strdup( |
|
|
|
|
service_config != nullptr ? service_config->json_string().c_str() |
|
|
|
|
: "")); |
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, |
|
|
|
|
"chand=%p: resolver returned updated service config: \"%s\"", |
|
|
|
|
chand_, service_config_json.get()); |
|
|
|
|
} |
|
|
|
|
// Save health check service name.
|
|
|
|
|
if (service_config != nullptr) { |
|
|
|
|
chand_->health_check_service_name_.reset( |
|
|
|
|
gpr_strdup(parsed_service_config->health_check_service_name())); |
|
|
|
|
} else { |
|
|
|
|
chand_->health_check_service_name_.reset(); |
|
|
|
|
} |
|
|
|
|
// Update health check service name used by existing subchannel wrappers.
|
|
|
|
|
for (auto* subchannel_wrapper : chand_->subchannel_wrappers_) { |
|
|
|
|
subchannel_wrapper->UpdateHealthCheckServiceName( |
|
|
|
|
grpc_core::UniquePtr<char>( |
|
|
|
|
gpr_strdup(chand_->health_check_service_name_.get()))); |
|
|
|
|
} |
|
|
|
|
// Save service config.
|
|
|
|
|
chand_->saved_service_config_ = std::move(service_config); |
|
|
|
|
} |
|
|
|
|
// Find LB policy config.
|
|
|
|
|
ProcessLbPolicy(result, parsed_service_config, |
|
|
|
|
&service_config_result.lb_policy_config); |
|
|
|
|
grpc_core::UniquePtr<char> lb_policy_name( |
|
|
|
|
gpr_strdup((service_config_result.lb_policy_config)->name())); |
|
|
|
|
// Swap out the data used by GetChannelInfo().
|
|
|
|
|
{ |
|
|
|
|
MutexLock lock(&chand_->info_mu_); |
|
|
|
|
chand_->info_lb_policy_name_ = std::move(lb_policy_name); |
|
|
|
|
if (service_config_json != nullptr) { |
|
|
|
|
chand_->info_service_config_json_ = std::move(service_config_json); |
|
|
|
|
} |
|
|
|
|
chand_->UpdateServiceConfigInControlPlaneLocked( |
|
|
|
|
std::move(service_config), parsed_service_config, |
|
|
|
|
service_config_result.lb_policy_config->name(), result.args); |
|
|
|
|
} |
|
|
|
|
// Return results.
|
|
|
|
|
return service_config_result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ChannelData::ChannelConfigHelper::ApplyConfigSelector( |
|
|
|
|
bool service_config_changed, |
|
|
|
|
RefCountedPtr<ConfigSelector> config_selector) { |
|
|
|
|
chand_->UpdateServiceConfigInDataPlaneLocked(service_config_changed, |
|
|
|
|
std::move(config_selector)); |
|
|
|
|
void ChannelData::ChannelConfigHelper::StartUsingServiceConfigForCalls() { |
|
|
|
|
chand_->UpdateServiceConfigInDataPlaneLocked(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ChannelData::ChannelConfigHelper::ResolverTransientFailure( |
|
|
|
@ -1560,21 +1507,19 @@ void ChannelData::ChannelConfigHelper::ResolverTransientFailure( |
|
|
|
|
chand_->resolver_transient_failure_error_ = error; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ChannelData::ChannelConfigHelper::ProcessLbPolicy( |
|
|
|
|
void ChannelData::ChannelConfigHelper::ChooseLbPolicy( |
|
|
|
|
const Resolver::Result& resolver_result, |
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config, |
|
|
|
|
RefCountedPtr<LoadBalancingPolicy::Config>* lb_policy_config) { |
|
|
|
|
// Prefer the LB policy config found in the service config.
|
|
|
|
|
if (parsed_service_config != nullptr && |
|
|
|
|
parsed_service_config->parsed_lb_config() != nullptr) { |
|
|
|
|
if (parsed_service_config->parsed_lb_config() != nullptr) { |
|
|
|
|
*lb_policy_config = parsed_service_config->parsed_lb_config(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
// Try the deprecated LB policy name from the service config.
|
|
|
|
|
// If not, try the setting from channel args.
|
|
|
|
|
const char* policy_name = nullptr; |
|
|
|
|
if (parsed_service_config != nullptr && |
|
|
|
|
!parsed_service_config->parsed_deprecated_lb_policy().empty()) { |
|
|
|
|
if (!parsed_service_config->parsed_deprecated_lb_policy().empty()) { |
|
|
|
|
policy_name = parsed_service_config->parsed_deprecated_lb_policy().c_str(); |
|
|
|
|
} else { |
|
|
|
|
const grpc_arg* channel_arg = |
|
|
|
@ -1694,16 +1639,16 @@ ChannelData::ChannelData(grpc_channel_element_args* args, grpc_error** error) |
|
|
|
|
"filter"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
// Get default service config
|
|
|
|
|
// Get default service config. If none is specified via the client API,
|
|
|
|
|
// we use an empty config.
|
|
|
|
|
const char* service_config_json = grpc_channel_arg_get_string( |
|
|
|
|
grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVICE_CONFIG)); |
|
|
|
|
if (service_config_json != nullptr) { |
|
|
|
|
*error = GRPC_ERROR_NONE; |
|
|
|
|
default_service_config_ = ServiceConfig::Create(service_config_json, error); |
|
|
|
|
if (*error != GRPC_ERROR_NONE) { |
|
|
|
|
default_service_config_.reset(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (service_config_json == nullptr) service_config_json = "{}"; |
|
|
|
|
*error = GRPC_ERROR_NONE; |
|
|
|
|
default_service_config_ = ServiceConfig::Create(service_config_json, error); |
|
|
|
|
if (*error != GRPC_ERROR_NONE) { |
|
|
|
|
default_service_config_.reset(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
grpc_uri* uri = grpc_uri_parse(server_uri, true); |
|
|
|
|
if (uri != nullptr && uri->path[0] != '\0') { |
|
|
|
@ -1755,7 +1700,6 @@ void ChannelData::UpdateStateAndPickerLocked( |
|
|
|
|
health_check_service_name_.reset(); |
|
|
|
|
saved_service_config_.reset(); |
|
|
|
|
saved_config_selector_.reset(); |
|
|
|
|
received_first_resolver_result_ = false; |
|
|
|
|
} |
|
|
|
|
// Update connectivity state.
|
|
|
|
|
state_tracker_.SetState(state, status, reason); |
|
|
|
@ -1823,51 +1767,56 @@ void ChannelData::UpdateStateAndPickerLocked( |
|
|
|
|
pending_subchannel_updates_.clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ChannelData::UpdateServiceConfigInDataPlaneLocked( |
|
|
|
|
bool service_config_changed, |
|
|
|
|
RefCountedPtr<ConfigSelector> config_selector) { |
|
|
|
|
// If the service config did not change and there is no new ConfigSelector,
|
|
|
|
|
// retain the old one (if any).
|
|
|
|
|
// TODO(roth): Consider whether this is really the right way to handle
|
|
|
|
|
// this. We might instead want to decide this in ApplyServiceConfig()
|
|
|
|
|
// where we decide whether to stick with the saved service config.
|
|
|
|
|
if (!service_config_changed && config_selector == nullptr) { |
|
|
|
|
config_selector = saved_config_selector_; |
|
|
|
|
} |
|
|
|
|
// Check if ConfigSelector has changed.
|
|
|
|
|
const bool config_selector_changed = |
|
|
|
|
saved_config_selector_ != config_selector; |
|
|
|
|
saved_config_selector_ = config_selector; |
|
|
|
|
// We want to set the service config at least once, even if the
|
|
|
|
|
// resolver does not return a config, because that ensures that we
|
|
|
|
|
// disable retries if they are not enabled in the service config.
|
|
|
|
|
// TODO(roth): Consider removing the received_first_resolver_result_ check
|
|
|
|
|
// when we implement transparent retries.
|
|
|
|
|
if (!service_config_changed && !config_selector_changed && |
|
|
|
|
received_first_resolver_result_) { |
|
|
|
|
return; |
|
|
|
|
void ChannelData::UpdateServiceConfigInControlPlaneLocked( |
|
|
|
|
RefCountedPtr<ServiceConfig> service_config, |
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config, |
|
|
|
|
const char* lb_policy_name, const grpc_channel_args* args) { |
|
|
|
|
grpc_core::UniquePtr<char> service_config_json( |
|
|
|
|
gpr_strdup(service_config->json_string().c_str())); |
|
|
|
|
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_routing_trace)) { |
|
|
|
|
gpr_log(GPR_INFO, |
|
|
|
|
"chand=%p: resolver returned updated service config: \"%s\"", this, |
|
|
|
|
service_config_json.get()); |
|
|
|
|
} |
|
|
|
|
// Save service config.
|
|
|
|
|
saved_service_config_ = std::move(service_config); |
|
|
|
|
// Save health check service name.
|
|
|
|
|
health_check_service_name_.reset( |
|
|
|
|
gpr_strdup(parsed_service_config->health_check_service_name())); |
|
|
|
|
// Update health check service name used by existing subchannel wrappers.
|
|
|
|
|
for (auto* subchannel_wrapper : subchannel_wrappers_) { |
|
|
|
|
subchannel_wrapper->UpdateHealthCheckServiceName(grpc_core::UniquePtr<char>( |
|
|
|
|
gpr_strdup(health_check_service_name_.get()))); |
|
|
|
|
} |
|
|
|
|
// Swap out the data used by GetChannelInfo().
|
|
|
|
|
grpc_core::UniquePtr<char> lb_policy_name_owned(gpr_strdup(lb_policy_name)); |
|
|
|
|
{ |
|
|
|
|
MutexLock lock(&info_mu_); |
|
|
|
|
info_lb_policy_name_ = std::move(lb_policy_name_owned); |
|
|
|
|
info_service_config_json_ = std::move(service_config_json); |
|
|
|
|
} |
|
|
|
|
received_first_resolver_result_ = true; |
|
|
|
|
// Save config selector.
|
|
|
|
|
saved_config_selector_ = ConfigSelector::GetFromChannelArgs(*args); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ChannelData::UpdateServiceConfigInDataPlaneLocked() { |
|
|
|
|
// Get retry throttle data from service config.
|
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config = |
|
|
|
|
static_cast<const internal::ClientChannelGlobalParsedConfig*>( |
|
|
|
|
saved_service_config_->GetGlobalParsedConfig( |
|
|
|
|
internal::ClientChannelServiceConfigParser::ParserIndex())); |
|
|
|
|
absl::optional<internal::ClientChannelGlobalParsedConfig::RetryThrottling> |
|
|
|
|
retry_throttle_config = parsed_service_config->retry_throttling(); |
|
|
|
|
RefCountedPtr<ServerRetryThrottleData> retry_throttle_data; |
|
|
|
|
if (saved_service_config_ != nullptr) { |
|
|
|
|
const internal::ClientChannelGlobalParsedConfig* parsed_service_config = |
|
|
|
|
static_cast<const internal::ClientChannelGlobalParsedConfig*>( |
|
|
|
|
saved_service_config_->GetGlobalParsedConfig( |
|
|
|
|
internal::ClientChannelServiceConfigParser::ParserIndex())); |
|
|
|
|
if (parsed_service_config != nullptr) { |
|
|
|
|
absl::optional<internal::ClientChannelGlobalParsedConfig::RetryThrottling> |
|
|
|
|
retry_throttle_config = parsed_service_config->retry_throttling(); |
|
|
|
|
if (retry_throttle_config.has_value()) { |
|
|
|
|
retry_throttle_data = |
|
|
|
|
internal::ServerRetryThrottleMap::GetDataForServer( |
|
|
|
|
server_name_.get(), |
|
|
|
|
retry_throttle_config.value().max_milli_tokens, |
|
|
|
|
retry_throttle_config.value().milli_token_ratio); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Create default config selector if not provided by resolver.
|
|
|
|
|
if (retry_throttle_config.has_value()) { |
|
|
|
|
retry_throttle_data = internal::ServerRetryThrottleMap::GetDataForServer( |
|
|
|
|
server_name_.get(), retry_throttle_config.value().max_milli_tokens, |
|
|
|
|
retry_throttle_config.value().milli_token_ratio); |
|
|
|
|
} |
|
|
|
|
// Grab ref to service config.
|
|
|
|
|
RefCountedPtr<ServiceConfig> service_config = saved_service_config_; |
|
|
|
|
// Grab ref to config selector. Use default if resolver didn't supply one.
|
|
|
|
|
RefCountedPtr<ConfigSelector> config_selector = saved_config_selector_; |
|
|
|
|
if (config_selector == nullptr) { |
|
|
|
|
config_selector = |
|
|
|
|
MakeRefCounted<DefaultConfigSelector>(saved_service_config_); |
|
|
|
@ -1876,9 +1825,6 @@ void ChannelData::UpdateServiceConfigInDataPlaneLocked( |
|
|
|
|
//
|
|
|
|
|
// We defer unreffing the old values (and deallocating memory) until
|
|
|
|
|
// after releasing the lock to keep the critical section small.
|
|
|
|
|
RefCountedPtr<ServiceConfig> service_config_to_unref = saved_service_config_; |
|
|
|
|
RefCountedPtr<ConfigSelector> config_selector_to_unref = |
|
|
|
|
std::move(config_selector); |
|
|
|
|
{ |
|
|
|
|
MutexLock lock(&data_plane_mu_); |
|
|
|
|
GRPC_ERROR_UNREF(resolver_transient_failure_error_); |
|
|
|
@ -1887,8 +1833,8 @@ void ChannelData::UpdateServiceConfigInDataPlaneLocked( |
|
|
|
|
received_service_config_data_ = true; |
|
|
|
|
// Old values will be unreffed after lock is released.
|
|
|
|
|
retry_throttle_data_.swap(retry_throttle_data); |
|
|
|
|
service_config_.swap(service_config_to_unref); |
|
|
|
|
config_selector_.swap(config_selector_to_unref); |
|
|
|
|
service_config_.swap(service_config); |
|
|
|
|
config_selector_.swap(config_selector); |
|
|
|
|
// Re-process queued picks.
|
|
|
|
|
for (QueuedPick* pick = queued_picks_; pick != nullptr; pick = pick->next) { |
|
|
|
|
grpc_call_element* elem = pick->elem; |
|
|
|
|