Skip to content

Commit

Permalink
Add full support for configuring exponential connection backoff params.
Browse files Browse the repository at this point in the history
  • Loading branch information
easwars committed Apr 2, 2019
1 parent f1437f7 commit 36ed5dd
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 55 deletions.
22 changes: 22 additions & 0 deletions backoff.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,34 @@ import (

// DefaultBackoffConfig uses values specified for backoff in
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
// Deprecated: use ConnectRetryParams instead.
var DefaultBackoffConfig = BackoffConfig{
MaxDelay: 120 * time.Second,
}

// BackoffConfig defines the parameters for the default gRPC backoff strategy.
// Deprecated: use ConnectRetryParams instead.
type BackoffConfig struct {
// MaxDelay is the upper bound of backoff delay.
MaxDelay time.Duration
}

// ConnectRetryParams defines the parameters for the backoff during connection
// retries. Users are encouraged to use this instead of BackoffConfig.
//
// This API is EXPERIMENTAL.
type ConnectRetryParams struct {
// BaseDelay is the amount of time to backoff after the first connection
// failure.
BaseDelay time.Duration
// Multiplier is the factor with which to multiply backoffs after a failed
// retry.
Multiplier float64
// Jitter is the factor with which backoffs are randomized.
Jitter float64
// MaxDelay is the upper bound of backoff delay.
MaxDelay time.Duration
// MinConnectTimeout is the minimum amount of time we are willing to give a
// connection to complete.
MinConnectTimeout time.Duration
}
8 changes: 3 additions & 5 deletions balancer/grpclb/grpclb.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,14 @@ const (
)

var (
// defaultBackoffConfig configures the backoff strategy that's used when the
// defaultBackoffStrategy configures the backoff strategy that's used when the
// init handshake in the RPC is unsuccessful. It's not for the clientconn
// reconnect backoff.
//
// It has the same value as the default grpc.DefaultBackoffConfig.
//
// TODO: make backoff configurable.
defaultBackoffConfig = backoff.Exponential{
MaxDelay: 120 * time.Second,
}
defaultBackoffStrategy = backoff.NewExponentialBuilder().Build()
errServerTerminatedConnection = errors.New("grpclb: failed to recv server list: server terminated connection")
)

Expand Down Expand Up @@ -177,7 +175,7 @@ func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) bal
scStates: make(map[balancer.SubConn]connectivity.State),
picker: &errPicker{err: balancer.ErrNoSubConnAvailable},
clientStats: newRPCStats(),
backoff: defaultBackoffConfig, // TODO: make backoff configurable.
backoff: defaultBackoffStrategy, // TODO: make backoff configurable.
}

var err error
Expand Down
8 changes: 2 additions & 6 deletions balancer/xds/xds_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,7 @@ const (
endpointRequired = "endpoints_required"
)

var (
defaultBackoffConfig = backoff.Exponential{
MaxDelay: 120 * time.Second,
}
)
var defaultBackoffStrategy = backoff.NewExponentialBuilder().Build()

// client is responsible for connecting to the specified traffic director, passing the received
// ADS response from the traffic director, and sending notification when communication with the
Expand Down Expand Up @@ -255,7 +251,7 @@ func newXDSClient(balancerName string, serviceName string, enableCDS bool, opts
newADS: newADS,
loseContact: loseContact,
cleanup: exitCleanup,
backoff: defaultBackoffConfig,
backoff: defaultBackoffStrategy,
}

c.ctx, c.cancel = context.WithCancel(context.Background())
Expand Down
6 changes: 2 additions & 4 deletions clientconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
}
}
if cc.dopts.bs == nil {
cc.dopts.bs = backoff.Exponential{
MaxDelay: DefaultBackoffConfig.MaxDelay,
}
cc.dopts.bs = backoff.NewExponentialBuilder().Build()
}
if cc.dopts.resolverBuilder == nil {
// Only try to parse target when resolver builder is not already set.
Expand Down Expand Up @@ -902,7 +900,7 @@ func (ac *addrConn) resetTransport() {
addrs := ac.addrs
backoffFor := ac.dopts.bs.Backoff(ac.backoffIdx)
// This will be the duration that dial gets to finish.
dialDuration := minConnectTimeout
dialDuration := ac.dopts.bs.MinConnectionTimeout()
if ac.dopts.minConnectTimeout != nil {
dialDuration = ac.dopts.minConnectTimeout()
}
Expand Down
3 changes: 2 additions & 1 deletion clientconn_state_transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ func (b *stateRecordingBalancerBuilder) nextStateNotifier() <-chan connectivity.

type noBackoff struct{}

func (b noBackoff) Backoff(int) time.Duration { return time.Duration(0) }
func (b noBackoff) Backoff(int) time.Duration { return time.Duration(0) }
func (b noBackoff) MinConnectionTimeout() time.Duration { return time.Duration(0) }

// Keep reading until something causes the connection to die (EOF, server
// closed, etc). Useful as a tool for mindlessly keeping the connection
Expand Down
37 changes: 22 additions & 15 deletions clientconn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,22 +777,31 @@ func (s) TestCredentialsMisuse(t *testing.T) {
}

func (s) TestWithBackoffConfigDefault(t *testing.T) {
testBackoffConfigSet(t, &DefaultBackoffConfig)
testBackoffStrategySet(t, backoff.NewExponentialBuilder().Build())
}

func (s) TestWithBackoffConfig(t *testing.T) {
b := BackoffConfig{MaxDelay: DefaultBackoffConfig.MaxDelay / 2}
expected := b
testBackoffConfigSet(t, &expected, WithBackoffConfig(b))
md := DefaultBackoffConfig.MaxDelay / 2
expected := backoff.NewExponentialBuilder().MaxDelay(md).Build()
testBackoffStrategySet(t, expected, WithBackoffConfig(BackoffConfig{MaxDelay: md}))
}

func (s) TestWithBackoffMaxDelay(t *testing.T) {
md := DefaultBackoffConfig.MaxDelay / 2
expected := BackoffConfig{MaxDelay: md}
testBackoffConfigSet(t, &expected, WithBackoffMaxDelay(md))
expected := backoff.NewExponentialBuilder().MaxDelay(md).Build()
testBackoffStrategySet(t, expected, WithBackoffMaxDelay(md))
}

func (s) TestWithConnectRetryParams(t *testing.T) {
bd := 2 * time.Second
mltpr := 2.0
jitter := 0.0
crt := ConnectRetryParams{BaseDelay: bd, Multiplier: mltpr, Jitter: jitter}
expected := backoff.NewExponentialBuilder().BaseDelay(bd).Multiplier(mltpr).Jitter(jitter).MaxDelay(time.Duration(0)).MinConnectTimeout(time.Duration(0)).Build()
testBackoffStrategySet(t, expected, WithConnectRetryParams(crt))
}

func testBackoffConfigSet(t *testing.T, expected *BackoffConfig, opts ...DialOption) {
func testBackoffStrategySet(t *testing.T, expected backoff.Strategy, opts ...DialOption) {
opts = append(opts, WithInsecure())
conn, err := Dial("passthrough:///foo:80", opts...)
if err != nil {
Expand All @@ -801,19 +810,16 @@ func testBackoffConfigSet(t *testing.T, expected *BackoffConfig, opts ...DialOpt
defer conn.Close()

if conn.dopts.bs == nil {
t.Fatalf("backoff config not set")
t.Fatalf("backoff strategy not set")
}

actual, ok := conn.dopts.bs.(backoff.Exponential)
if !ok {
t.Fatalf("unexpected type of backoff config: %#v", conn.dopts.bs)
t.Fatalf("unexpected type of backoff strategy: %#v", conn.dopts.bs)
}

expectedValue := backoff.Exponential{
MaxDelay: expected.MaxDelay,
}
if actual != expectedValue {
t.Fatalf("unexpected backoff config on connection: %v, want %v", actual, expected)
if actual != expected.(backoff.Exponential) {
t.Errorf("unexpected backoff strategy on connection: %v, want %v", actual, expected)
}
}

Expand Down Expand Up @@ -1016,7 +1022,8 @@ func (s) TestGetClientConnTarget(t *testing.T) {

type backoffForever struct{}

func (b backoffForever) Backoff(int) time.Duration { return time.Duration(math.MaxInt64) }
func (b backoffForever) Backoff(int) time.Duration { return time.Duration(math.MaxInt64) }
func (b backoffForever) MinConnectionTimeout() time.Duration { return time.Duration(0) }

func (s) TestResetConnectBackoff(t *testing.T) {
dials := make(chan struct{})
Expand Down
22 changes: 17 additions & 5 deletions dialoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,21 +247,33 @@ func WithServiceConfig(c <-chan ServiceConfig) DialOption {
})
}

// WithConnectRetryParams configures the dialer to use the provided backoff
// parameters for the algorithm defined in
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
// This will override all the default values with the ones provided here. So,
// use with caution.
//
// This API is EXPERIMENTAL.
func WithConnectRetryParams(p ConnectRetryParams) DialOption {
b := backoff.NewExponentialBuilder()
b.BaseDelay(p.BaseDelay).Multiplier(p.Multiplier).Jitter(p.Jitter).MaxDelay(p.MaxDelay).MinConnectTimeout(p.MinConnectTimeout)
return withBackoff(b.Build())
}

// WithBackoffMaxDelay configures the dialer to use the provided maximum delay
// when backing off after failed connection attempts.
//
// Deprecated: use WithConnectRetryParams instead.
func WithBackoffMaxDelay(md time.Duration) DialOption {
return WithBackoffConfig(BackoffConfig{MaxDelay: md})
}

// WithBackoffConfig configures the dialer to use the provided backoff
// parameters after connection failures.
//
// Use WithBackoffMaxDelay until more parameters on BackoffConfig are opened up
// for use.
// Deprecated: use WithConnectRetryParams instead.
func WithBackoffConfig(b BackoffConfig) DialOption {
return withBackoff(backoff.Exponential{
MaxDelay: b.MaxDelay,
})
return withBackoff(backoff.NewExponentialBuilder().MaxDelay(b.MaxDelay).Build())
}

// withBackoff sets the backoff strategy used for connectRetryNum after a failed
Expand Down
4 changes: 1 addition & 3 deletions health/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import (
"google.golang.org/grpc/status"
)

const maxDelay = 120 * time.Second

var backoffStrategy = backoff.Exponential{MaxDelay: maxDelay}
var backoffStrategy = backoff.NewExponentialBuilder().Build()
var backoffFunc = func(ctx context.Context, retries int) bool {
d := backoffStrategy.Backoff(retries)
timer := time.NewTimer(d)
Expand Down
Loading

0 comments on commit 36ed5dd

Please sign in to comment.