transport: flatten RetryConfig to value type, replace nil sentinel with Disabled bool
This commit is contained in:
+11
-30
@@ -20,10 +20,11 @@ type ConnectionConfig struct {
|
||||
PingInterval time.Duration
|
||||
IncomingBufferSize int
|
||||
ErrorsBufferSize int
|
||||
Retry *RetryConfig
|
||||
Retry RetryConfig
|
||||
}
|
||||
|
||||
type RetryConfig struct {
|
||||
Disabled bool
|
||||
MaxRetries int
|
||||
InitialDelay time.Duration
|
||||
MaxDelay time.Duration
|
||||
@@ -55,16 +56,12 @@ func GetDefaultConnectionConfig() *ConnectionConfig {
|
||||
PingInterval: 20 * time.Second,
|
||||
IncomingBufferSize: 100,
|
||||
ErrorsBufferSize: 10,
|
||||
Retry: GetDefaultRetryConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func GetDefaultRetryConfig() *RetryConfig {
|
||||
return &RetryConfig{
|
||||
MaxRetries: 0, // Infinite retries
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 60 * time.Second,
|
||||
JitterFactor: 0.2,
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 0, // Infinite retries
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 60 * time.Second,
|
||||
JitterFactor: 0.2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +82,7 @@ func ValidateConnectionConfig(config *ConnectionConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.Retry != nil {
|
||||
if !config.Retry.Disabled {
|
||||
err = validateMaxRetries(config.Retry.MaxRetries)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -223,19 +220,15 @@ func WithErrorsBufferSize(value int) ConnectionOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutRetry() ConnectionOption {
|
||||
func WithRetryDisabled() ConnectionOption {
|
||||
return func(c *ConnectionConfig) error {
|
||||
c.Retry = nil
|
||||
c.Retry.Disabled = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRetryMaxRetries(value int) ConnectionOption {
|
||||
return func(c *ConnectionConfig) error {
|
||||
if c.Retry == nil {
|
||||
c.Retry = GetDefaultRetryConfig()
|
||||
}
|
||||
|
||||
err := validateMaxRetries(value)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -248,10 +241,6 @@ func WithRetryMaxRetries(value int) ConnectionOption {
|
||||
|
||||
func WithRetryInitialDelay(value time.Duration) ConnectionOption {
|
||||
return func(c *ConnectionConfig) error {
|
||||
if c.Retry == nil {
|
||||
c.Retry = GetDefaultRetryConfig()
|
||||
}
|
||||
|
||||
err := validateInitialDelay(value)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -264,10 +253,6 @@ func WithRetryInitialDelay(value time.Duration) ConnectionOption {
|
||||
|
||||
func WithRetryMaxDelay(value time.Duration) ConnectionOption {
|
||||
return func(c *ConnectionConfig) error {
|
||||
if c.Retry == nil {
|
||||
c.Retry = GetDefaultRetryConfig()
|
||||
}
|
||||
|
||||
err := validateMaxDelay(value)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -280,10 +265,6 @@ func WithRetryMaxDelay(value time.Duration) ConnectionOption {
|
||||
|
||||
func WithRetryJitterFactor(value float64) ConnectionOption {
|
||||
return func(c *ConnectionConfig) error {
|
||||
if c.Retry == nil {
|
||||
c.Retry = GetDefaultRetryConfig()
|
||||
}
|
||||
|
||||
err := validateJitterFactor(value)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+11
-17
@@ -35,18 +35,12 @@ func TestDefaultConnectionConfig(t *testing.T) {
|
||||
PingInterval: 20 * time.Second,
|
||||
IncomingBufferSize: 100,
|
||||
ErrorsBufferSize: 10,
|
||||
Retry: GetDefaultRetryConfig(),
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultRetryConnectionConfig(t *testing.T) {
|
||||
conf := GetDefaultRetryConfig()
|
||||
|
||||
assert.Equal(t, conf, &RetryConfig{
|
||||
MaxRetries: 0,
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 60 * time.Second,
|
||||
JitterFactor: 0.2,
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 0,
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 60 * time.Second,
|
||||
JitterFactor: 0.2,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -114,10 +108,10 @@ func TestWithWriteTimeout(t *testing.T) {
|
||||
func TestWithRetry(t *testing.T) {
|
||||
t.Run("without retry", func(t *testing.T) {
|
||||
conf := GetDefaultConnectionConfig()
|
||||
opt := WithoutRetry()
|
||||
opt := WithRetryDisabled()
|
||||
err := applyConnectionOptions(conf, opt)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, conf.Retry)
|
||||
assert.True(t, conf.Retry.Disabled)
|
||||
})
|
||||
|
||||
t.Run("with attempts", func(t *testing.T) {
|
||||
@@ -209,7 +203,7 @@ func TestValidateConnectionConfig(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "valid empty",
|
||||
conf: *&ConnectionConfig{},
|
||||
conf: ConnectionConfig{Retry: RetryConfig{Disabled: true}},
|
||||
},
|
||||
{
|
||||
name: "valid defaults",
|
||||
@@ -220,7 +214,7 @@ func TestValidateConnectionConfig(t *testing.T) {
|
||||
conf: ConnectionConfig{
|
||||
CloseHandler: (func(code int, text string) error { return nil }),
|
||||
WriteTimeout: time.Duration(30),
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 0,
|
||||
InitialDelay: 2 * time.Second,
|
||||
MaxDelay: 10 * time.Second,
|
||||
@@ -231,7 +225,7 @@ func TestValidateConnectionConfig(t *testing.T) {
|
||||
{
|
||||
name: "invalid - initial delay > max delay",
|
||||
conf: ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
InitialDelay: 10 * time.Second,
|
||||
MaxDelay: 1 * time.Second,
|
||||
},
|
||||
|
||||
@@ -102,7 +102,7 @@ func TestConnectionSend(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("write timeout disabled when zero", func(t *testing.T) {
|
||||
config := &ConnectionConfig{WriteTimeout: 0}
|
||||
config := &ConnectionConfig{WriteTimeout: 0, Retry: RetryConfig{Disabled: true}}
|
||||
|
||||
outgoingData := make(chan honeybeetest.MockOutgoingData, 10)
|
||||
mockSocket := honeybeetest.NewMockSocket()
|
||||
@@ -148,7 +148,7 @@ func TestConnectionSend(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("write timeout sets deadline when positive", func(t *testing.T) {
|
||||
config := &ConnectionConfig{WriteTimeout: 30 * time.Millisecond}
|
||||
config := &ConnectionConfig{WriteTimeout: 30 * time.Millisecond, Retry: RetryConfig{Disabled: true}}
|
||||
|
||||
outgoingData := make(chan honeybeetest.MockOutgoingData, 10)
|
||||
mockSocket := honeybeetest.NewMockSocket()
|
||||
@@ -194,7 +194,7 @@ func TestConnectionSend(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("send fails on deadline error", func(t *testing.T) {
|
||||
config := &ConnectionConfig{WriteTimeout: 1 * time.Millisecond}
|
||||
config := &ConnectionConfig{WriteTimeout: 1 * time.Millisecond, Retry: RetryConfig{Disabled: true}}
|
||||
|
||||
mockSocket := honeybeetest.NewMockSocket()
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestNewConnection(t *testing.T) {
|
||||
{
|
||||
name: "valid url, valid config",
|
||||
url: "wss://relay.example.com:8080/path",
|
||||
config: &ConnectionConfig{WriteTimeout: 30 * time.Second},
|
||||
config: &ConnectionConfig{WriteTimeout: 30 * time.Second, Retry: RetryConfig{Disabled: true}},
|
||||
},
|
||||
{
|
||||
name: "invalid url",
|
||||
@@ -82,7 +82,7 @@ func TestNewConnection(t *testing.T) {
|
||||
name: "invalid config",
|
||||
url: "ws://example.com",
|
||||
config: &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
InitialDelay: 10 * time.Second,
|
||||
MaxDelay: 1 * time.Second,
|
||||
},
|
||||
@@ -152,13 +152,13 @@ func TestNewConnectionFromSocket(t *testing.T) {
|
||||
{
|
||||
name: "valid socket with valid config",
|
||||
socket: honeybeetest.NewMockSocket(),
|
||||
config: &ConnectionConfig{WriteTimeout: 30 * time.Second},
|
||||
config: &ConnectionConfig{WriteTimeout: 30 * time.Second, Retry: RetryConfig{Disabled: true}},
|
||||
},
|
||||
{
|
||||
name: "invalid config",
|
||||
socket: honeybeetest.NewMockSocket(),
|
||||
config: &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
InitialDelay: 10 * time.Second,
|
||||
MaxDelay: 1 * time.Second,
|
||||
},
|
||||
@@ -173,6 +173,7 @@ func TestNewConnectionFromSocket(t *testing.T) {
|
||||
CloseHandler: func(code int, text string) error {
|
||||
return nil
|
||||
},
|
||||
Retry: RetryConfig{Disabled: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -299,7 +300,7 @@ func TestConnect(t *testing.T) {
|
||||
|
||||
t.Run("connect retries on dial failure", func(t *testing.T) {
|
||||
config := &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 2,
|
||||
InitialDelay: 1 * time.Millisecond,
|
||||
MaxDelay: 5 * time.Millisecond,
|
||||
@@ -331,7 +332,7 @@ func TestConnect(t *testing.T) {
|
||||
|
||||
t.Run("connect fails after max retries", func(t *testing.T) {
|
||||
config := &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 2,
|
||||
InitialDelay: 1 * time.Millisecond,
|
||||
MaxDelay: 5 * time.Millisecond,
|
||||
@@ -382,6 +383,7 @@ func TestConnect(t *testing.T) {
|
||||
CloseHandler: func(code int, text string) error {
|
||||
return nil
|
||||
},
|
||||
Retry: RetryConfig{Disabled: true},
|
||||
}
|
||||
conn, err := NewConnection(context.Background(), "ws://test", config, nil)
|
||||
assert.NoError(t, err)
|
||||
@@ -429,7 +431,7 @@ func TestConnect(t *testing.T) {
|
||||
func TestConnectContextCancellation(t *testing.T) {
|
||||
t.Run("context cancelled during connect returns before retries exhaust", func(t *testing.T) {
|
||||
config := &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 100,
|
||||
InitialDelay: 500 * time.Millisecond,
|
||||
MaxDelay: 1 * time.Second,
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestConnectLogging(t *testing.T) {
|
||||
mockHandler := honeybeetest.NewMockSlogHandler()
|
||||
|
||||
config := &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 2,
|
||||
InitialDelay: 1 * time.Millisecond,
|
||||
MaxDelay: 5 * time.Millisecond,
|
||||
@@ -101,7 +101,7 @@ func TestConnectLogging(t *testing.T) {
|
||||
mockHandler := honeybeetest.NewMockSlogHandler()
|
||||
|
||||
config := &ConnectionConfig{
|
||||
Retry: &RetryConfig{
|
||||
Retry: RetryConfig{
|
||||
MaxRetries: 3,
|
||||
InitialDelay: 1 * time.Millisecond,
|
||||
MaxDelay: 5 * time.Millisecond,
|
||||
@@ -279,7 +279,7 @@ func TestWriterLogging(t *testing.T) {
|
||||
t.Run("write deadline error", func(t *testing.T) {
|
||||
mockHandler := honeybeetest.NewMockSlogHandler()
|
||||
|
||||
config := &ConnectionConfig{WriteTimeout: 1 * time.Millisecond}
|
||||
config := &ConnectionConfig{WriteTimeout: 1 * time.Millisecond, Retry: RetryConfig{Disabled: true}}
|
||||
|
||||
deadlineErr := fmt.Errorf("deadline error")
|
||||
mockSocket := honeybeetest.NewMockSocket()
|
||||
|
||||
+6
-6
@@ -7,16 +7,16 @@ import (
|
||||
)
|
||||
|
||||
type RetryManager struct {
|
||||
config *RetryConfig
|
||||
config RetryConfig
|
||||
retryCount int
|
||||
saturation int
|
||||
}
|
||||
|
||||
func NewRetryManager(config *RetryConfig) *RetryManager {
|
||||
func NewRetryManager(config RetryConfig) *RetryManager {
|
||||
// saturationCount: retry count at which base delay meets or exceeds MaxDelay.
|
||||
// Conservative by two to preserve jitter variance near the boundary.
|
||||
saturation := 0
|
||||
if config != nil &&
|
||||
if !config.Disabled &&
|
||||
config.InitialDelay > 0 &&
|
||||
config.InitialDelay <= config.MaxDelay {
|
||||
ratio := float64(config.MaxDelay) / float64(config.InitialDelay)
|
||||
@@ -31,7 +31,7 @@ func NewRetryManager(config *RetryConfig) *RetryManager {
|
||||
}
|
||||
|
||||
func (r *RetryManager) ShouldRetry() bool {
|
||||
if r.config == nil {
|
||||
if r.config.Disabled {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (r *RetryManager) ShouldRetry() bool {
|
||||
}
|
||||
|
||||
func (r *RetryManager) CalculateDelay() time.Duration {
|
||||
if r.config == nil {
|
||||
if r.config.Disabled {
|
||||
return time.Second
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (r *RetryManager) CalculateDelay() time.Duration {
|
||||
|
||||
// if saturation is reached, calculated backoff will always be higher than
|
||||
// the maximum delay
|
||||
if r.config != nil && r.retryCount >= r.saturation {
|
||||
if r.retryCount >= r.saturation {
|
||||
return r.config.MaxDelay
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewRetryManager(t *testing.T) {
|
||||
config := &RetryConfig{
|
||||
config := RetryConfig{
|
||||
MaxRetries: 0,
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ func TestNewRetryManager(t *testing.T) {
|
||||
assert.Equal(t, config, mgr.config)
|
||||
assert.Equal(t, 0, mgr.retryCount)
|
||||
|
||||
// Should accept nil config
|
||||
mgr = NewRetryManager(nil)
|
||||
assert.Nil(t, mgr.config)
|
||||
// Should accept a disabled config
|
||||
mgr = NewRetryManager(RetryConfig{Disabled: true})
|
||||
assert.True(t, mgr.config.Disabled)
|
||||
assert.Equal(t, 0, mgr.retryCount)
|
||||
}
|
||||
|
||||
func TestRecordRetry(t *testing.T) {
|
||||
mgr := NewRetryManager(nil)
|
||||
mgr := NewRetryManager(RetryConfig{Disabled: true})
|
||||
assert.Equal(t, mgr.retryCount, 0)
|
||||
|
||||
mgr.RecordRetry()
|
||||
@@ -34,13 +34,13 @@ func TestRecordRetry(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldRetry(t *testing.T) {
|
||||
// never retry if config is nil
|
||||
mgr := NewRetryManager(nil)
|
||||
// never retry if config is disabled
|
||||
mgr := NewRetryManager(RetryConfig{Disabled: true})
|
||||
assert.False(t, mgr.ShouldRetry())
|
||||
|
||||
// always retry if max attempt count is zero
|
||||
mgr = &RetryManager{
|
||||
config: &RetryConfig{
|
||||
config: RetryConfig{
|
||||
MaxRetries: 0,
|
||||
},
|
||||
retryCount: 1000,
|
||||
@@ -49,7 +49,7 @@ func TestShouldRetry(t *testing.T) {
|
||||
|
||||
// retry if below max attempt count
|
||||
mgr = &RetryManager{
|
||||
config: &RetryConfig{
|
||||
config: RetryConfig{
|
||||
MaxRetries: 10,
|
||||
},
|
||||
retryCount: 5,
|
||||
@@ -58,7 +58,7 @@ func TestShouldRetry(t *testing.T) {
|
||||
|
||||
// do not retry if above max attempt count
|
||||
mgr = &RetryManager{
|
||||
config: &RetryConfig{
|
||||
config: RetryConfig{
|
||||
MaxRetries: 10,
|
||||
},
|
||||
retryCount: 11,
|
||||
@@ -68,12 +68,12 @@ func TestShouldRetry(t *testing.T) {
|
||||
|
||||
func TestCalculateDelayDisabled(t *testing.T) {
|
||||
// default delay if retry is disabled
|
||||
mgr := NewRetryManager(nil)
|
||||
mgr := NewRetryManager(RetryConfig{Disabled: true})
|
||||
assert.Equal(t, time.Second, mgr.CalculateDelay())
|
||||
}
|
||||
|
||||
func TestCalculateDelayWithoutJitter(t *testing.T) {
|
||||
mgr := NewRetryManager(&RetryConfig{
|
||||
mgr := NewRetryManager(RetryConfig{
|
||||
MaxRetries: 0,
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 5 * time.Second,
|
||||
@@ -105,7 +105,7 @@ func TestCalculateDelayWithoutJitter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCalculateDelayWithJitter(t *testing.T) {
|
||||
mgr := NewRetryManager(&RetryConfig{
|
||||
mgr := NewRetryManager(RetryConfig{
|
||||
MaxRetries: 0,
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 5 * time.Second,
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestAcquireSocket(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
retryMgr := NewRetryManager(&RetryConfig{
|
||||
retryMgr := NewRetryManager(RetryConfig{
|
||||
MaxRetries: tc.maxRetries,
|
||||
InitialDelay: 1 * time.Millisecond,
|
||||
MaxDelay: 5 * time.Millisecond,
|
||||
@@ -106,7 +106,8 @@ func TestAcquireSocketGuards(t *testing.T) {
|
||||
return honeybeetest.NewMockSocket(), nil, nil
|
||||
},
|
||||
}
|
||||
validRetryMgr := NewRetryManager(GetDefaultRetryConfig())
|
||||
validRetryConfig := GetDefaultConnectionConfig().Retry
|
||||
validRetryMgr := NewRetryManager(validRetryConfig)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -167,7 +168,7 @@ func TestAcquireSocketContextCancellation(t *testing.T) {
|
||||
// cancel before acquiring socket
|
||||
cancel()
|
||||
|
||||
retryMgr := NewRetryManager(GetDefaultRetryConfig())
|
||||
retryMgr := NewRetryManager(GetDefaultConnectionConfig().Retry)
|
||||
_, _, err := AcquireSocket(ctx, retryMgr, mockDialer, "ws://test", nil, nil)
|
||||
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
@@ -186,7 +187,7 @@ func TestAcquireSocketContextCancellation(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
retryMgr := NewRetryManager(&RetryConfig{
|
||||
retryMgr := NewRetryManager(RetryConfig{
|
||||
MaxRetries: 10,
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 1 * time.Second,
|
||||
@@ -230,7 +231,7 @@ func TestAcquireSocketContextCancellation(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
retryMgr := NewRetryManager(GetDefaultRetryConfig())
|
||||
retryMgr := NewRetryManager(GetDefaultConnectionConfig().Retry)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, _, err := AcquireSocket(ctx, retryMgr, mockDialer, "ws://test", nil, nil)
|
||||
@@ -263,7 +264,7 @@ func TestAcquireSocketPassesHeaders(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
retryMgr := NewRetryManager(&RetryConfig{MaxRetries: 0})
|
||||
retryMgr := NewRetryManager(RetryConfig{MaxRetries: 0, InitialDelay: 1 * time.Millisecond, MaxDelay: 5 * time.Millisecond})
|
||||
_, _, err := AcquireSocket(context.Background(), retryMgr, mockDialer, "ws://test", header, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user