mirror of https://github.com/grpc/grpc.git
Move retry code into its own filter in the DynamicFilter stack (#25820)
* rename ChannelData to ClientChannel * make ClientChannel class definition public * move retry code to its own filter * move LB call factory method to ClientChannel class * move dynamic termination filter out of ClientChannel class * update comments * remove retry parsing from client channel service config parser * fix clang-tidy * fix service_config_test * clang-formatpull/25848/head
parent
efd2ed8ae6
commit
3f19333ced
29 changed files with 3803 additions and 3463 deletions
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@ |
||||
//
|
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RETRY_FILTER_H |
||||
#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RETRY_FILTER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/channel/channel_stack.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
extern const grpc_channel_filter kRetryFilterVtable; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RETRY_FILTER_H
|
@ -0,0 +1,285 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/retry_service_config.h" |
||||
|
||||
#include <ctype.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/client_channel.h" |
||||
#include "src/core/ext/filters/client_channel/lb_policy_registry.h" |
||||
#include "src/core/ext/filters/client_channel/server_address.h" |
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/channel/status_util.h" |
||||
#include "src/core/lib/gpr/string.h" |
||||
#include "src/core/lib/gprpp/memory.h" |
||||
#include "src/core/lib/json/json_util.h" |
||||
#include "src/core/lib/uri/uri_parser.h" |
||||
|
||||
// As per the retry design, we do not allow more than 5 retry attempts.
|
||||
#define MAX_MAX_RETRY_ATTEMPTS 5 |
||||
|
||||
namespace grpc_core { |
||||
namespace internal { |
||||
|
||||
namespace { |
||||
size_t g_retry_service_config_parser_index; |
||||
} |
||||
|
||||
size_t RetryServiceConfigParser::ParserIndex() { |
||||
return g_retry_service_config_parser_index; |
||||
} |
||||
|
||||
void RetryServiceConfigParser::Register() { |
||||
g_retry_service_config_parser_index = ServiceConfigParser::RegisterParser( |
||||
absl::make_unique<RetryServiceConfigParser>()); |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
grpc_error* ParseRetryThrottling(const Json& json, intptr_t* max_milli_tokens, |
||||
intptr_t* milli_token_ratio) { |
||||
if (json.type() != Json::Type::OBJECT) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling error:Type should be object"); |
||||
} |
||||
std::vector<grpc_error*> error_list; |
||||
// Parse maxTokens.
|
||||
auto it = json.object_value().find("maxTokens"); |
||||
if (it == json.object_value().end()) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:maxTokens error:Not found")); |
||||
} else if (it->second.type() != Json::Type::NUMBER) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:maxTokens error:Type should be " |
||||
"number")); |
||||
} else { |
||||
*max_milli_tokens = |
||||
gpr_parse_nonnegative_int(it->second.string_value().c_str()) * 1000; |
||||
if (*max_milli_tokens <= 0) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:maxTokens error:should be " |
||||
"greater than zero")); |
||||
} |
||||
} |
||||
// Parse tokenRatio.
|
||||
it = json.object_value().find("tokenRatio"); |
||||
if (it == json.object_value().end()) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:tokenRatio error:Not found")); |
||||
} else if (it->second.type() != Json::Type::NUMBER) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:tokenRatio error:type should be " |
||||
"number")); |
||||
} else { |
||||
// We support up to 3 decimal digits.
|
||||
size_t whole_len = it->second.string_value().size(); |
||||
const char* value = it->second.string_value().c_str(); |
||||
uint32_t multiplier = 1; |
||||
uint32_t decimal_value = 0; |
||||
const char* decimal_point = strchr(value, '.'); |
||||
if (decimal_point != nullptr) { |
||||
whole_len = static_cast<size_t>(decimal_point - value); |
||||
multiplier = 1000; |
||||
size_t decimal_len = strlen(decimal_point + 1); |
||||
if (decimal_len > 3) decimal_len = 3; |
||||
if (!gpr_parse_bytes_to_uint32(decimal_point + 1, decimal_len, |
||||
&decimal_value)) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:tokenRatio error:Failed " |
||||
"parsing")); |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("retryThrottling", &error_list); |
||||
} |
||||
uint32_t decimal_multiplier = 1; |
||||
for (size_t i = 0; i < (3 - decimal_len); ++i) { |
||||
decimal_multiplier *= 10; |
||||
} |
||||
decimal_value *= decimal_multiplier; |
||||
} |
||||
uint32_t whole_value; |
||||
if (!gpr_parse_bytes_to_uint32(value, whole_len, &whole_value)) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:tokenRatio error:Failed " |
||||
"parsing")); |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("retryThrottling", &error_list); |
||||
} |
||||
*milli_token_ratio = |
||||
static_cast<int>((whole_value * multiplier) + decimal_value); |
||||
if (*milli_token_ratio <= 0) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryThrottling field:tokenRatio error:value should " |
||||
"be greater than 0")); |
||||
} |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("retryThrottling", &error_list); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> |
||||
RetryServiceConfigParser::ParseGlobalParams(const grpc_channel_args* /*args*/, |
||||
const Json& json, |
||||
grpc_error** error) { |
||||
GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE); |
||||
auto it = json.object_value().find("retryThrottling"); |
||||
if (it == json.object_value().end()) return nullptr; |
||||
intptr_t max_milli_tokens = 0; |
||||
intptr_t milli_token_ratio = 0; |
||||
*error = |
||||
ParseRetryThrottling(it->second, &max_milli_tokens, &milli_token_ratio); |
||||
if (*error != GRPC_ERROR_NONE) return nullptr; |
||||
return absl::make_unique<RetryGlobalConfig>(max_milli_tokens, |
||||
milli_token_ratio); |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
grpc_error* ParseRetryPolicy(const Json& json, int* max_attempts, |
||||
grpc_millis* initial_backoff, |
||||
grpc_millis* max_backoff, |
||||
float* backoff_multiplier, |
||||
StatusCodeSet* retryable_status_codes) { |
||||
if (json.type() != Json::Type::OBJECT) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryPolicy error:should be of type object"); |
||||
} |
||||
std::vector<grpc_error*> error_list; |
||||
// Parse maxAttempts.
|
||||
auto it = json.object_value().find("maxAttempts"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::NUMBER) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:maxAttempts error:should be of type number")); |
||||
} else { |
||||
*max_attempts = |
||||
gpr_parse_nonnegative_int(it->second.string_value().c_str()); |
||||
if (*max_attempts <= 1) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:maxAttempts error:should be at least 2")); |
||||
} else if (*max_attempts > MAX_MAX_RETRY_ATTEMPTS) { |
||||
gpr_log(GPR_ERROR, |
||||
"service config: clamped retryPolicy.maxAttempts at %d", |
||||
MAX_MAX_RETRY_ATTEMPTS); |
||||
*max_attempts = MAX_MAX_RETRY_ATTEMPTS; |
||||
} |
||||
} |
||||
} |
||||
// Parse initialBackoff.
|
||||
if (ParseJsonObjectFieldAsDuration(json.object_value(), "initialBackoff", |
||||
initial_backoff, &error_list) && |
||||
*initial_backoff == 0) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:initialBackoff error:must be greater than 0")); |
||||
} |
||||
// Parse maxBackoff.
|
||||
if (ParseJsonObjectFieldAsDuration(json.object_value(), "maxBackoff", |
||||
max_backoff, &error_list) && |
||||
*max_backoff == 0) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:maxBackoff error:should be greater than 0")); |
||||
} |
||||
// Parse backoffMultiplier.
|
||||
it = json.object_value().find("backoffMultiplier"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::NUMBER) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:backoffMultiplier error:should be of type number")); |
||||
} else { |
||||
if (sscanf(it->second.string_value().c_str(), "%f", backoff_multiplier) != |
||||
1) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:backoffMultiplier error:failed to parse")); |
||||
} else if (*backoff_multiplier <= 0) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:backoffMultiplier error:should be greater than 0")); |
||||
} |
||||
} |
||||
} |
||||
// Parse retryableStatusCodes.
|
||||
it = json.object_value().find("retryableStatusCodes"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::ARRAY) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryableStatusCodes error:should be of type array")); |
||||
} else { |
||||
for (const Json& element : it->second.array_value()) { |
||||
if (element.type() != Json::Type::STRING) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryableStatusCodes error:status codes should be of type " |
||||
"string")); |
||||
continue; |
||||
} |
||||
grpc_status_code status; |
||||
if (!grpc_status_code_from_string(element.string_value().c_str(), |
||||
&status)) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryableStatusCodes error:failed to parse status code")); |
||||
continue; |
||||
} |
||||
retryable_status_codes->Add(status); |
||||
} |
||||
if (retryable_status_codes->Empty()) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryableStatusCodes error:should be non-empty")); |
||||
}; |
||||
} |
||||
} |
||||
// Make sure required fields are set.
|
||||
if (error_list.empty()) { |
||||
if (*max_attempts == 0 || *initial_backoff == 0 || *max_backoff == 0 || |
||||
*backoff_multiplier == 0 || retryable_status_codes->Empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"field:retryPolicy error:Missing required field(s)"); |
||||
} |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("retryPolicy", &error_list); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> |
||||
RetryServiceConfigParser::ParsePerMethodParams( |
||||
const grpc_channel_args* /*args*/, const Json& json, grpc_error** error) { |
||||
GPR_DEBUG_ASSERT(error != nullptr && *error == GRPC_ERROR_NONE); |
||||
// Parse retry policy.
|
||||
auto it = json.object_value().find("retryPolicy"); |
||||
if (it == json.object_value().end()) return nullptr; |
||||
int max_attempts = 0; |
||||
grpc_millis initial_backoff = 0; |
||||
grpc_millis max_backoff = 0; |
||||
float backoff_multiplier = 0; |
||||
StatusCodeSet retryable_status_codes; |
||||
*error = ParseRetryPolicy(it->second, &max_attempts, &initial_backoff, |
||||
&max_backoff, &backoff_multiplier, |
||||
&retryable_status_codes); |
||||
if (*error != GRPC_ERROR_NONE) return nullptr; |
||||
return absl::make_unique<RetryMethodConfig>(max_attempts, initial_backoff, |
||||
max_backoff, backoff_multiplier, |
||||
retryable_status_codes); |
||||
} |
||||
|
||||
} // namespace internal
|
||||
} // namespace grpc_core
|
@ -0,0 +1,90 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RETRY_SERVICE_CONFIG_H |
||||
#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RETRY_SERVICE_CONFIG_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <memory> |
||||
|
||||
#include "src/core/ext/filters/client_channel/retry_throttle.h" |
||||
#include "src/core/ext/filters/client_channel/service_config_parser.h" |
||||
#include "src/core/lib/channel/status_util.h" |
||||
#include "src/core/lib/iomgr/exec_ctx.h" // for grpc_millis |
||||
|
||||
namespace grpc_core { |
||||
namespace internal { |
||||
|
||||
class RetryGlobalConfig : public ServiceConfigParser::ParsedConfig { |
||||
public: |
||||
RetryGlobalConfig(intptr_t max_milli_tokens, intptr_t milli_token_ratio) |
||||
: max_milli_tokens_(max_milli_tokens), |
||||
milli_token_ratio_(milli_token_ratio) {} |
||||
|
||||
intptr_t max_milli_tokens() const { return max_milli_tokens_; } |
||||
intptr_t milli_token_ratio() const { return milli_token_ratio_; } |
||||
|
||||
private: |
||||
intptr_t max_milli_tokens_ = 0; |
||||
intptr_t milli_token_ratio_ = 0; |
||||
}; |
||||
|
||||
class RetryMethodConfig : public ServiceConfigParser::ParsedConfig { |
||||
public: |
||||
RetryMethodConfig(int max_attempts, grpc_millis initial_backoff, |
||||
grpc_millis max_backoff, float backoff_multiplier, |
||||
StatusCodeSet retryable_status_codes) |
||||
: max_attempts_(max_attempts), |
||||
initial_backoff_(initial_backoff), |
||||
max_backoff_(max_backoff), |
||||
backoff_multiplier_(backoff_multiplier), |
||||
retryable_status_codes_(retryable_status_codes) {} |
||||
|
||||
int max_attempts() const { return max_attempts_; } |
||||
grpc_millis initial_backoff() const { return initial_backoff_; } |
||||
grpc_millis max_backoff() const { return max_backoff_; } |
||||
float backoff_multiplier() const { return backoff_multiplier_; } |
||||
StatusCodeSet retryable_status_codes() const { |
||||
return retryable_status_codes_; |
||||
} |
||||
|
||||
private: |
||||
int max_attempts_ = 0; |
||||
grpc_millis initial_backoff_ = 0; |
||||
grpc_millis max_backoff_ = 0; |
||||
float backoff_multiplier_ = 0; |
||||
StatusCodeSet retryable_status_codes_; |
||||
}; |
||||
|
||||
class RetryServiceConfigParser : public ServiceConfigParser::Parser { |
||||
public: |
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> ParseGlobalParams( |
||||
const grpc_channel_args* /*args*/, const Json& json, |
||||
grpc_error** error) override; |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> ParsePerMethodParams( |
||||
const grpc_channel_args* /*args*/, const Json& json, |
||||
grpc_error** error) override; |
||||
|
||||
static size_t ParserIndex(); |
||||
static void Register(); |
||||
}; |
||||
|
||||
} // namespace internal
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RETRY_SERVICE_CONFIG_H
|
Loading…
Reference in new issue