mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-07-26 07:48:13 +00:00
61dae6c605
Signed-off-by: Noste <83548733+Noooste@users.noreply.github.com>
105 lines
2.3 KiB
Go
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
|
|
}
|