Skip to content

Commit 68cb3d5

Browse files
sync upstream
1 parent 177a70c commit 68cb3d5

File tree

4 files changed

+232
-0
lines changed

4 files changed

+232
-0
lines changed

pkg/retry/v1/config.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package retry
2+
3+
import (
4+
"strings"
5+
"time"
6+
7+
"google.golang.org/grpc/codes"
8+
)
9+
10+
//revive:enable:var-naming
11+
12+
type RetryConfig struct {
13+
mc map[nameConfig]*methodConfig
14+
}
15+
16+
type RetryOption func(c *RetryConfig)
17+
18+
type grpcRetryPolicy struct {
19+
MethodConfig []*methodConfig `json:"methodConfig"`
20+
}
21+
22+
type methodConfig struct {
23+
NameConfig []nameConfig `json:"name"`
24+
RetryPolicy retryPolicy `json:"retryPolicy"`
25+
RetryThrottling retryThrottling `json:"retryThrottling"`
26+
WaitForReady bool `json:"waitForReady"`
27+
}
28+
29+
type nameConfig struct {
30+
Service string `json:"service,omitempty"`
31+
Method string `json:"method,omitempty"`
32+
}
33+
34+
type retryPolicy struct {
35+
MaxAttempts int `json:"maxAttempts"`
36+
InitialBackoff Duration `json:"initialBackoff"`
37+
MaxBackoff Duration `json:"maxBackoff"`
38+
BackoffMultiplier float64 `json:"backoffMultiplier"`
39+
RetryableStatusCodes []string `json:"retryableStatusCodes"`
40+
}
41+
42+
type retryThrottling struct {
43+
MaxTokens int `json:"maxTokens"`
44+
TokenRatio float64 `json:"tokenRatio"`
45+
}
46+
47+
type GRPCKeepAliveConfig struct {
48+
Time time.Duration `yaml:"time" validate:"required"`
49+
Timeout time.Duration `yaml:"timeout" validate:"required"`
50+
PermitWithoutStream bool `yaml:"permit_without_stream"`
51+
}
52+
53+
func DefaultRetryConfig() *RetryConfig {
54+
return &RetryConfig{}
55+
}
56+
57+
func DefaultNameConfig() nameConfig {
58+
return nameConfig{}
59+
}
60+
61+
func NewNameConfig(service, method string) nameConfig {
62+
return nameConfig{
63+
Service: service,
64+
Method: method,
65+
}
66+
}
67+
68+
func WithDefaultRetryConfig() RetryOption {
69+
return func(c *RetryConfig) {
70+
c = defaultRetryConfig()
71+
}
72+
}
73+
74+
func WithRetries(nm nameConfig, n int) RetryOption {
75+
return func(c *RetryConfig) {
76+
if c == nil {
77+
c = defaultRetryConfig()
78+
}
79+
80+
if _, ok := c.mc[nm]; !ok {
81+
c.mc[nm] = defaultMethodConfig()
82+
c.mc[nm].NameConfig = []nameConfig{nm}
83+
}
84+
85+
c.mc[nm].RetryPolicy.MaxAttempts = n
86+
}
87+
}
88+
89+
func WithRetryableStatusCodes(nm nameConfig, codes ...codes.Code) RetryOption {
90+
return func(c *RetryConfig) {
91+
if c == nil {
92+
c = defaultRetryConfig()
93+
}
94+
95+
names := make([]string, len(codes))
96+
for i, code := range codes {
97+
names[i] = strings.ToUpper(code.String())
98+
}
99+
100+
if _, ok := c.mc[nm]; !ok {
101+
c.mc[nm] = defaultMethodConfig()
102+
c.mc[nm].NameConfig = []nameConfig{nm}
103+
}
104+
105+
c.mc[nm].RetryPolicy.RetryableStatusCodes = names
106+
}
107+
}

pkg/retry/v1/default.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package retry
2+
3+
import "time"
4+
5+
func defaultNameConfig() nameConfig {
6+
return nameConfig{}
7+
}
8+
9+
func defaultRetryConfig() *RetryConfig {
10+
return &RetryConfig{
11+
mc: map[nameConfig]*methodConfig{
12+
// default value for all services and methods
13+
defaultNameConfig(): defaultMethodConfig(),
14+
},
15+
}
16+
}
17+
18+
func defaultMethodConfig() *methodConfig {
19+
return &methodConfig{
20+
NameConfig: []nameConfig{{}},
21+
RetryPolicy: retryPolicy{
22+
MaxAttempts: 4,
23+
InitialBackoff: Duration(time.Millisecond * 100),
24+
MaxBackoff: Duration(time.Second * 20),
25+
BackoffMultiplier: 2,
26+
RetryableStatusCodes: []string{"UNAVAILABLE"},
27+
},
28+
RetryThrottling: retryThrottling{
29+
MaxTokens: 100,
30+
TokenRatio: 0.1,
31+
},
32+
WaitForReady: true,
33+
}
34+
}

pkg/retry/v1/dial_option.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package retry
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"google.golang.org/grpc"
8+
)
9+
10+
// RetryDialOption returns DialOption that configures the default
11+
// service config, which will be used in cases where:
12+
//
13+
// 1. User provide service config in SDK build after RetryDialOption function.
14+
// In this case retries configuration override user's service config.
15+
//
16+
// 2. User don't get service config through xDS. In that case service config
17+
// from xDS override retries configuration.
18+
//
19+
// It's recommended to use default configuration (DefaultRetryDialOption)
20+
// for retries in SDK to avoid retry amplification.
21+
func RetryDialOption(opts ...RetryOption) (grpc.DialOption, error) {
22+
config := defaultRetryConfig()
23+
for _, opt := range opts {
24+
opt(config)
25+
}
26+
27+
if config.mc == nil {
28+
return nil, fmt.Errorf("can't provide retry config with this options")
29+
}
30+
31+
mc := []*methodConfig{}
32+
for _, v := range config.mc {
33+
mc = append(mc, v)
34+
}
35+
36+
c, err := json.Marshal(&grpcRetryPolicy{MethodConfig: mc})
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
return grpc.WithDefaultServiceConfig(string(c)), nil
42+
}
43+
44+
// DefaultRetryDialOption returns DialOption that configures the default
45+
// service config, which will be used in cases where:
46+
//
47+
// 1. User provide service config in SDK build after DefaultRetryDialOption function.
48+
// In this case retries configuration override user's service config.
49+
//
50+
// 2. User don't get service config through xDS. In that case service config
51+
// from xDS override retries configuration.
52+
func DefaultRetryDialOption() (grpc.DialOption, error) {
53+
return RetryDialOption(WithDefaultRetryConfig())
54+
}

pkg/retry/v1/duration.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package retry
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
)
8+
9+
// Duration defines JSON marshal and unmarshal methods to conform to the
10+
// protobuf JSON spec defined [here].
11+
//
12+
// [here]: https://protobuf.dev/reference/protobuf/google.protobuf/#duration
13+
type Duration time.Duration
14+
15+
func (d Duration) String() string {
16+
return fmt.Sprint(time.Duration(d))
17+
}
18+
19+
// MarshalJSON converts from d to a JSON string output.
20+
func (d Duration) MarshalJSON() ([]byte, error) {
21+
ns := time.Duration(d).Nanoseconds()
22+
sec := ns / int64(time.Second)
23+
ns = ns % int64(time.Second)
24+
25+
var sign string
26+
if sec < 0 || ns < 0 {
27+
sign, sec, ns = "-", -1*sec, -1*ns
28+
}
29+
30+
// Generated output always contains 0, 3, 6, or 9 fractional digits,
31+
// depending on required precision.
32+
str := fmt.Sprintf("%s%d.%09d", sign, sec, ns)
33+
str = strings.TrimSuffix(str, "000")
34+
str = strings.TrimSuffix(str, "000")
35+
str = strings.TrimSuffix(str, ".000")
36+
return []byte(fmt.Sprintf("\"%ss\"", str)), nil
37+
}

0 commit comments

Comments
 (0)