Files
garage-ui/backend/pkg/utils/retry.go
T

105 lines
2.3 KiB
Go

package utils
import (
"context"
"errors"
"fmt"
"net"
"syscall"
"time"
)
// RetryConfig configures retry behavior
type RetryConfig struct {
MaxRetries int
InitialBackoff time.Duration
MaxBackoff time.Duration
BackoffFactor float64
}
// DefaultRetryConfig returns default retry configuration
func DefaultRetryConfig() RetryConfig {
return RetryConfig{
MaxRetries: 3,
InitialBackoff: 100 * time.Millisecond,
MaxBackoff: 5 * time.Second,
BackoffFactor: 2.0,
}
}
// IsConnectionRefused checks if the error is a connection refused error
func IsConnectionRefused(err error) bool {
if err == nil {
return false
}
// Check for connection refused error
var opErr *net.OpError
if errors.As(err, &opErr) {
// Check if it's a connection refused error
if opErr.Op == "dial" || opErr.Op == "read" {
var syscallErr *syscall.Errno
if errors.As(opErr.Err, &syscallErr) {
return *syscallErr == syscall.ECONNREFUSED
}
}
}
// Also check for "connection refused" in error message as fallback
if errors.Is(err, syscall.ECONNREFUSED) {
return true
}
return false
}
// RetryWithBackoff executes a function with exponential backoff on connection refused errors
func RetryWithBackoff(ctx context.Context, config RetryConfig, fn func() error) error {
var lastErr error
for attempt := 0; attempt <= config.MaxRetries; attempt++ {
// Execute the function
err := fn()
if err == nil {
return nil
}
lastErr = err
// If it's not a connection refused error, don't retry
if !IsConnectionRefused(err) {
return err
}
// If this was the last attempt, return the error
if attempt == config.MaxRetries {
return fmt.Errorf("max retries (%d) exceeded: %w", config.MaxRetries, err)
}
// Calculate backoff duration with exponential increase
backoff := time.Duration(float64(config.InitialBackoff) * pow(config.BackoffFactor, float64(attempt)))
if backoff > config.MaxBackoff {
backoff = config.MaxBackoff
}
// Wait with context cancellation support
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled during retry: %w", ctx.Err())
case <-time.After(backoff):
// Continue to next attempt
}
}
return lastErr
}
// pow is a simple integer exponentiation helper
func pow(base float64, exp float64) float64 {
result := 1.0
for i := 0; i < int(exp); i++ {
result *= base
}
return result
}