Documentation
¶
Index ¶
- func Do(ctx context.Context, cfg Config, operation Operation) error
- func DoWithNotify(ctx context.Context, cfg Config, operation Operation, ...) error
- func Permanent(err error) error
- type Config
- type Operation
- type Option
- func WithInitialInterval(interval time.Duration) Option
- func WithMaxInterval(interval time.Duration) Option
- func WithMaxRetries(maxRetries uint64) Option
- func WithMultiplier(multiplier float64) Option
- func WithName(name string) Option
- func WithOTelConfig(otelConfig *pkgotel.Config) Option
- func WithRandomizationFactor(factor float64) Option
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Do ¶
Do executes the operation with retry logic using exponential backoff. It returns nil if the operation succeeds, or the last error if all retries are exhausted. An invalid Config (see Config field docs) is reported as an error before the first attempt; Do never panics.
Example:
cfg := retry.New(
retry.WithName("database.connect"),
retry.WithMaxRetries(3),
retry.WithOTelConfig(otelConfig),
)
err := retry.Do(ctx, cfg, func(ctx context.Context) error {
return db.Ping()
})
Example ¶
Do retries the operation with exponential backoff until it succeeds. Here the operation fails twice, then succeeds on the third attempt.
package main
import (
"context"
"errors"
"fmt"
"time"
"github.com/jasoet/pkg/v3/retry"
)
func main() {
cfg := retry.New(
retry.WithName("flaky.op"),
retry.WithMaxRetries(5),
retry.WithInitialInterval(time.Millisecond),
retry.WithRandomizationFactor(0), // disable jitter for deterministic timing
)
attempts := 0
err := retry.Do(context.Background(), cfg, func(ctx context.Context) error {
attempts++
if attempts < 3 {
return errors.New("temporary failure")
}
return nil
})
fmt.Println("attempts:", attempts)
fmt.Println("err:", err)
}
Output: attempts: 3 err: <nil>
func DoWithNotify ¶
func DoWithNotify( ctx context.Context, cfg Config, operation Operation, notifyFunc func(error, time.Duration), ) error
DoWithNotify is like Do but calls notifyFunc on each retry with the error and backoff duration. This is useful for custom logging or metrics.
Example:
err := retry.DoWithNotify(ctx, cfg, operation, func(err error, backoff time.Duration) {
log.Printf("Retry after error: %v (waiting %v)", err, backoff)
})
Example ¶
DoWithNotify calls the notify function before each retry wait. With RandomizationFactor 0 the backoff durations are exact powers of the multiplier: 10ms, then 20ms.
package main
import (
"context"
"fmt"
"time"
"github.com/jasoet/pkg/v3/retry"
)
func main() {
cfg := retry.New(
retry.WithName("notify.op"),
retry.WithMaxRetries(3),
retry.WithInitialInterval(10*time.Millisecond),
retry.WithMultiplier(2.0),
retry.WithRandomizationFactor(0),
)
attempts := 0
err := retry.DoWithNotify(context.Background(), cfg,
func(ctx context.Context) error {
attempts++
if attempts < 3 {
return fmt.Errorf("failure #%d", attempts)
}
return nil
},
func(err error, backoff time.Duration) {
fmt.Printf("retrying in %v: %v\n", backoff, err)
},
)
fmt.Println("attempts:", attempts)
fmt.Println("err:", err)
}
Output: retrying in 10ms: failure #1 retrying in 20ms: failure #2 attempts: 3 err: <nil>
func Permanent ¶
Permanent wraps an error to indicate it should not be retried. Use this when an error is not transient and retrying would be pointless.
Example:
return retry.Permanent(fmt.Errorf("invalid configuration"))
Example ¶
Permanent marks an error as non-retryable: Do stops after the first attempt. The returned error wraps the original one, so errors.Is/As still match it.
package main
import (
"context"
"errors"
"fmt"
"time"
"github.com/jasoet/pkg/v3/retry"
)
func main() {
cfg := retry.New(
retry.WithName("validate.input"),
retry.WithMaxRetries(5),
retry.WithInitialInterval(time.Millisecond),
)
attempts := 0
err := retry.Do(context.Background(), cfg, func(ctx context.Context) error {
attempts++
return retry.Permanent(errors.New("invalid input"))
})
fmt.Println("attempts:", attempts)
fmt.Println("err:", err)
}
Output: attempts: 1 err: validate.input failed after 1 attempts (1 initial + 0 retries): invalid input
Types ¶
type Config ¶
type Config struct {
// MaxRetries is the maximum number of retries after the initial attempt
// (0 means unlimited retries). With MaxRetries = N the operation is called
// at most N+1 times: 1 initial attempt plus up to N retries.
// Default: 5
MaxRetries uint64 `yaml:"maxRetries" mapstructure:"maxRetries"`
// InitialInterval is the initial retry interval.
// Default: 500ms
InitialInterval time.Duration `yaml:"initialInterval" mapstructure:"initialInterval"`
// MaxInterval caps the maximum retry interval.
// Default: 60s
MaxInterval time.Duration `yaml:"maxInterval" mapstructure:"maxInterval"`
// Multiplier is the exponential backoff multiplier. Must be > 1.
// Default: 2.0 (each retry waits 2x longer)
Multiplier float64 `yaml:"multiplier" mapstructure:"multiplier"`
// RandomizationFactor adds jitter to backoff intervals to prevent thundering herd.
// Must be in [0, 1]. 0.0 means no randomization, 0.5 means +/-50% jitter.
// Default: 0.5
RandomizationFactor float64 `yaml:"randomizationFactor" mapstructure:"randomizationFactor"`
// Name is the operation name used for logging and tracing.
// Default: "retry.operation"
Name string `yaml:"name" mapstructure:"name"`
// OTelConfig enables OpenTelemetry tracing and logging.
// Optional: if nil, no OTel instrumentation.
OTelConfig *pkgotel.Config `yaml:"-" mapstructure:"-"` // Not serializable from config files
}
Config holds retry configuration with exponential backoff.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults.
func New ¶
New returns a Config starting from DefaultConfig with each option applied in order.
Example ¶
New starts from DefaultConfig and applies each option in order.
package main
import (
"fmt"
"time"
"github.com/jasoet/pkg/v3/retry"
)
func main() {
cfg := retry.New(
retry.WithName("db.connect"),
retry.WithMaxRetries(3),
retry.WithInitialInterval(100*time.Millisecond),
)
fmt.Println("name:", cfg.Name)
fmt.Println("maxRetries:", cfg.MaxRetries)
fmt.Println("initialInterval:", cfg.InitialInterval)
// Untouched fields keep their defaults:
fmt.Println("maxInterval:", cfg.MaxInterval)
fmt.Println("multiplier:", cfg.Multiplier)
}
Output: name: db.connect maxRetries: 3 initialInterval: 100ms maxInterval: 1m0s multiplier: 2
type Operation ¶
Operation is a function that may fail and should be retried. Return nil to indicate success, or an error to trigger a retry.
type Option ¶
type Option func(*Config)
Option configures a Config. Options never panic; invalid values are reported as an error by Do and DoWithNotify before the first attempt.
func WithInitialInterval ¶
WithInitialInterval sets the initial retry interval.
func WithMaxInterval ¶
WithMaxInterval sets the maximum retry interval.
func WithMaxRetries ¶
WithMaxRetries sets the maximum number of retries after the initial attempt.
func WithMultiplier ¶
WithMultiplier sets the exponential backoff multiplier. Must be > 1; invalid values make Do and DoWithNotify return an error.
func WithOTelConfig ¶
WithOTelConfig adds OpenTelemetry configuration to the retry config.
func WithRandomizationFactor ¶
WithRandomizationFactor sets the jitter factor for backoff intervals. A value of 0.5 means intervals will vary by +/-50%. Set to 0.0 to disable jitter. Must be in [0, 1]; invalid values make Do and DoWithNotify return an error.