Documentation
¶
Overview ¶
Package retry implements a generic and customizable retry mechanism.
Took some inspiration from: - https://github.com/eapache/go-resiliency/tree/main/retrier
Example (RetryCustomClassifier) ¶
package main
import (
"errors"
"fmt"
"log/slog"
"os"
"time"
"github.com/Pix4D/go-kit/retry"
)
// Used in [ExampleRetry_CustomClassifier].
var ErrBananaUnavailable = errors.New("banana service unavailable")
// Embedded in [BananaResponseError].
type BananaResponse struct {
Amount int
}
// Used in [ExampleRetry_CustomClassifier].
type BananaResponseError struct {
Response *BananaResponse
}
func (eb BananaResponseError) Error() string {
return "look at my fields, there is more information there"
}
func main() {
rtr := retry.Retry{
UpTo: 30 * time.Second,
FirstDelay: 2 * time.Second,
BackoffLimit: 1 * time.Minute,
Log: slog.New(slog.NewTextHandler(os.Stdout,
&slog.HandlerOptions{ReplaceAttr: removeTime})),
SleepFn: func(d time.Duration) {}, // Only for the test!
}
attempt := 0
workFn := func() error {
attempt++
if attempt == 3 {
// Error wrapping is optional; we do it to show that it works also.
return fmt.Errorf("workFn: %w",
BananaResponseError{Response: &BananaResponse{Amount: 42}})
}
if attempt < 5 {
return ErrBananaUnavailable
}
// On 5th attempt we finally succeed.
return nil
}
classifierFn := func(err error) retry.Action {
var bananaResponseErr BananaResponseError
if errors.As(err, &bananaResponseErr) {
response := bananaResponseErr.Response
if response.Amount == 42 {
return retry.SoftFail
}
return retry.HardFail
}
if errors.Is(err, ErrBananaUnavailable) {
return retry.SoftFail
}
if err != nil {
return retry.HardFail
}
return retry.Success
}
err := rtr.Do(retry.ExponentialBackoff, classifierFn, workFn)
if err != nil {
// Handle error...
fmt.Println("error:", err)
}
}
// removeTime removes time-dependent attributes from log/slog records, making
// the output of testable examples [1] deterministic.
// [1]: https://go.dev/blog/examples
func removeTime(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
}
Output: level=INFO msg=waiting system=retry attempt=1 delay=2s totalDelay=2s level=INFO msg=waiting system=retry attempt=2 delay=4s totalDelay=6s level=INFO msg=waiting system=retry attempt=3 delay=8s totalDelay=14s level=INFO msg=waiting system=retry attempt=4 delay=16s totalDelay=30s level=INFO msg=success system=retry attempt=5 totalDelay=30s
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConstantBackoff ¶
Types ¶
type Action ¶
type Action int
Action is returned by a ClassifierFunc to indicate to Retry how to proceed.
const ( // Success informs Retry that the attempt has been a success. Success Action = iota // HardFail informs Retry that the attempt has been a hard failure and // thus should abort retrying. HardFail // SoftFail informs Retry that the attempt has been a soft failure and // thus should keep retrying. SoftFail )
type BackoffFunc ¶
BackoffFunc returns the next backoff duration; called by Retry.Do. You can use one of the ready-made functions ConstantBackoff, ExponentialBackoff or write your own. Parameter err allows to optionally inspect the error that caused the retry and return a custom delay; this can be used in special cases such as when rate-limited with a fixed window; for an example see github.com/Pix4D/go-kit/github.Backoff.
type ClassifierFunc ¶
ClassifierFunc decides whether to proceed or not; called by Retry.Do. Parameter err allows to inspect the error; for an example see github.com/Pix4D/go-kit/github.Classifier
type Retry ¶
type Retry struct {
UpTo time.Duration // Total maximum duration of the retries.
FirstDelay time.Duration // Duration of the first backoff.
BackoffLimit time.Duration // Upper bound duration of a backoff.
Log *slog.Logger
SleepFn func(d time.Duration) // Optional; used only to override in tests.
}
Retry is the controller of the retry mechanism. See the examples in file retry_example_test.go.
Example ¶
package main
import (
"fmt"
"log/slog"
"os"
"time"
"github.com/Pix4D/go-kit/retry"
)
func main() {
rtr := retry.Retry{
UpTo: 5 * time.Second,
FirstDelay: 1 * time.Second,
BackoffLimit: 1 * time.Second,
Log: slog.New(slog.NewTextHandler(os.Stdout,
&slog.HandlerOptions{ReplaceAttr: removeTime})),
}
workFn := func() error {
// Do work...
// If something fails, as usual, return error.
// Everything went well.
return nil
}
classifierFn := func(err error) retry.Action {
if err != nil {
return retry.SoftFail
}
return retry.Success
}
err := rtr.Do(retry.ConstantBackoff, classifierFn, workFn)
if err != nil {
// Handle error...
fmt.Println("error:", err)
}
}
// removeTime removes time-dependent attributes from log/slog records, making
// the output of testable examples [1] deterministic.
// [1]: https://go.dev/blog/examples
func removeTime(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
}
Output: level=INFO msg=success system=retry attempt=1 totalDelay=0s
func (Retry) Do ¶
func (rtr Retry) Do( backoffFn BackoffFunc, classifierFn ClassifierFunc, workFn WorkFunc, ) error
Do is the loop of Retry. See the examples in file retry_example_test.go.