diff --git a/backoff.go b/backoff.go index d0113ec9..36602caa 100644 --- a/backoff.go +++ b/backoff.go @@ -8,7 +8,7 @@ import ( // DefaultBackoffConfig uses values specified for backoff in // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. var ( - DefaultBackoffConfig = &BackoffConfig{ + DefaultBackoffConfig = BackoffConfig{ MaxDelay: 120 * time.Second, baseDelay: 1.0 * time.Second, factor: 1.6, @@ -33,7 +33,10 @@ type BackoffConfig struct { // MaxDelay is the upper bound of backoff delay. MaxDelay time.Duration - // TODO(stevvooe): The following fields are not exported, as allowing changes + // TODO(stevvooe): The following fields are not exported, as allowing + // changes would violate the current GRPC specification for backoff. If + // GRPC decides to allow more interesting backoff strategies, these fields + // may be opened up in the future. // baseDelay is the amount of time to wait before retrying after the first // failure. @@ -46,7 +49,16 @@ type BackoffConfig struct { jitter float64 } -func (bc *BackoffConfig) backoff(retries int) (t time.Duration) { +func (bc *BackoffConfig) setDefaults() { + md := bc.MaxDelay + *bc = DefaultBackoffConfig + + if md > 0 { + bc.MaxDelay = md + } +} + +func (bc BackoffConfig) backoff(retries int) (t time.Duration) { if retries == 0 { return bc.baseDelay } diff --git a/backoff_test.go b/backoff_test.go new file mode 100644 index 00000000..847858c1 --- /dev/null +++ b/backoff_test.go @@ -0,0 +1,11 @@ +package grpc + +import "testing" + +func TestBackoffConfigDefaults(t *testing.T) { + b := BackoffConfig{} + b.setDefaults() + if b != DefaultBackoffConfig { + t.Fatalf("expected BackoffConfig to pickup default parameters: %v != %v", b, DefaultBackoffConfig) + } +} diff --git a/clientconn.go b/clientconn.go index 1562c0f9..9b29eabd 100644 --- a/clientconn.go +++ b/clientconn.go @@ -115,9 +115,21 @@ func WithPicker(p Picker) DialOption { } } +// WithBackoffMaxDelay configures the dialer to use the provided maximum delay +// when backing off after failed connection attempts. +func WithBackoffMaxDelay(md time.Duration) DialOption { + return WithBackoffConfig(BackoffConfig{MaxDelay: md}) +} + // WithBackoffConfig configures the dialer to use the provided backoff // parameters after connection failures. -func WithBackoffConfig(b *BackoffConfig) DialOption { +// +// Use WithBackoffMaxDelay until more parameters on BackoffConfig are opened up +// for use. +func WithBackoffConfig(b BackoffConfig) DialOption { + // Set defaults to ensure that provided BackoffConfig is valid and + // unexported fields get default values. + b.setDefaults() return withBackoff(b) } diff --git a/clientconn_test.go b/clientconn_test.go index 8eb1a225..336ad4dd 100644 --- a/clientconn_test.go +++ b/clientconn_test.go @@ -80,3 +80,42 @@ func TestCredentialsMisuse(t *testing.T) { t.Fatalf("Dial(_, _) = _, %v, want _, %v", err, ErrCredentialsMisuse) } } + +func TestWithBackoffConfigDefault(t *testing.T) { + testBackoffConfigSet(t, &DefaultBackoffConfig) +} + +func TestWithBackoffConfig(t *testing.T) { + b := BackoffConfig{MaxDelay: DefaultBackoffConfig.MaxDelay / 2} + expected := b + expected.setDefaults() // defaults should be set + testBackoffConfigSet(t, &expected, WithBackoffConfig(b)) +} + +func TestWithBackoffMaxDelay(t *testing.T) { + md := DefaultBackoffConfig.MaxDelay / 2 + expected := BackoffConfig{MaxDelay: md} + expected.setDefaults() + testBackoffConfigSet(t, &expected, WithBackoffMaxDelay(md)) +} + +func testBackoffConfigSet(t *testing.T, expected *BackoffConfig, opts ...DialOption) { + opts = append(opts, WithInsecure()) + conn, err := Dial("foo:80", opts...) + if err != nil { + t.Fatalf("unexpected error dialing connection: %v", err) + } + + if conn.dopts.bs == nil { + t.Fatalf("backoff config not set") + } + + actual, ok := conn.dopts.bs.(BackoffConfig) + if !ok { + t.Fatalf("unexpected type of backoff config: %#v", conn.dopts.bs) + } + + if actual != *expected { + t.Fatalf("unexpected backoff config on connection: %v, want %v", actual, expected) + } +}