feat: initial goget release — modern IPv6-first download utility
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RetryConfig configures the retry logic
|
||||
type RetryConfig struct {
|
||||
// Maximum number of attempts
|
||||
MaxRetries int
|
||||
|
||||
// Initial delay between attempts
|
||||
InitialDelay time.Duration
|
||||
|
||||
// Maximum delay between attempts
|
||||
MaxDelay time.Duration
|
||||
|
||||
// Multiplier for exponential backoff
|
||||
Multiplier float64
|
||||
|
||||
// Retry on these HTTP status codes
|
||||
RetryableStatusCodes []int
|
||||
|
||||
// Callback before each retry
|
||||
OnRetry func(attempt int, err error, delay time.Duration)
|
||||
}
|
||||
|
||||
// DefaultRetryConfig returns the default configuration
|
||||
func DefaultRetryConfig() *RetryConfig {
|
||||
return &RetryConfig{
|
||||
MaxRetries: 3,
|
||||
InitialDelay: 1 * time.Second,
|
||||
MaxDelay: 30 * time.Second,
|
||||
Multiplier: 2.0,
|
||||
RetryableStatusCodes: []int{408, 429, 500, 502, 503, 504},
|
||||
}
|
||||
}
|
||||
|
||||
// RetryFunc is a function that can be retried
|
||||
type RetryFunc func(ctx context.Context, attempt int) error
|
||||
|
||||
// Retry performs an operation with retry logic
|
||||
func Retry(ctx context.Context, fn RetryFunc, cfg *RetryConfig) error {
|
||||
if cfg == nil {
|
||||
cfg = DefaultRetryConfig()
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
delay := cfg.InitialDelay
|
||||
|
||||
for attempt := 1; attempt <= cfg.MaxRetries+1; attempt++ {
|
||||
// Check the context
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// Perform the operation
|
||||
err := fn(ctx, attempt)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
|
||||
// Last attempt - return error
|
||||
if attempt > cfg.MaxRetries {
|
||||
return fmt.Errorf("all %d attempts failed: %w", cfg.MaxRetries+1, err)
|
||||
}
|
||||
|
||||
// Callback before retry
|
||||
if cfg.OnRetry != nil {
|
||||
cfg.OnRetry(attempt, err, delay)
|
||||
}
|
||||
|
||||
// Wait before the next attempt
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(delay):
|
||||
// Exponential backoff
|
||||
delay = time.Duration(float64(delay) * cfg.Multiplier)
|
||||
if delay > cfg.MaxDelay {
|
||||
delay = cfg.MaxDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// IsRetryableError determines whether the error is retryable
|
||||
func IsRetryableError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errStr := err.Error()
|
||||
|
||||
// Network errors
|
||||
retryableErrors := []string{
|
||||
"connection refused",
|
||||
"connection reset",
|
||||
"connection timed out",
|
||||
"i/o timeout",
|
||||
"temporary failure",
|
||||
"no route to host",
|
||||
}
|
||||
|
||||
for _, re := range retryableErrors {
|
||||
if containsIgnoreCase(errStr, re) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRetryableStatusCode determines whether the HTTP status is retryable
|
||||
func IsRetryableStatusCode(status int, cfg *RetryConfig) bool {
|
||||
if cfg == nil {
|
||||
cfg = DefaultRetryConfig()
|
||||
}
|
||||
|
||||
for _, code := range cfg.RetryableStatusCodes {
|
||||
if status == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// WithMaxRetries creates a RetryConfig with the specified maximum retries
|
||||
func WithMaxRetries(maxRetries int) *RetryConfig {
|
||||
cfg := DefaultRetryConfig()
|
||||
if maxRetries > 0 {
|
||||
cfg.MaxRetries = maxRetries
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// containsIgnoreCase helper function
|
||||
func containsIgnoreCase(s, substr string) bool {
|
||||
s = strings.ToLower(s)
|
||||
substr = strings.ToLower(substr)
|
||||
return strings.Contains(s, substr)
|
||||
}
|
||||
Reference in New Issue
Block a user