Skip to content

Instantly share code, notes, and snippets.

@erdii
Created August 2, 2023 09:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erdii/d321ba5d56d06f3a1c832e854ea58b76 to your computer and use it in GitHub Desktop.
Save erdii/d321ba5d56d06f3a1c832e854ea58b76 to your computer and use it in GitHub Desktop.
golang retry func() error
package retry
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
)
// retry retries a `func() error` up to `attempts` times, sleeping for the duration `sleep`*2^`attempt`.
// It will swallow all errors except the last one.
func retry(ctx context.Context, log logr.Logger, attempts int, sleep time.Duration, f func() error) error {
log = log.
WithName("retry").
WithValues("attempts", attempts, "sleep", sleep)
var err error
i := 0
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
// bail if too many retry attempts have already been made
if i >= attempts {
return fmt.Errorf("gave up after %d attempts. last error: %w", attempts, err)
}
// sleep if we're not on the first iteration because that means that a previous iteration errored
if i > 0 {
log.Error(err, "function errored", "i", i)
time.Sleep(sleep)
sleep *= 2
}
// try to run `f()`
if err = f(); err == nil {
return nil
}
i++
}
}
}
package retry
import (
"context"
"fmt"
"testing"
"time"
"github.com/go-logr/logr"
"github.com/stretchr/testify/require"
)
func failingFuncFactory(failures int, err error) func() error {
i := 0
return func() error {
if i < failures {
i++
return err
}
return nil
}
}
var errNever = fmt.Errorf("never gonna give you up")
func TestRetry(t *testing.T) {
t.Parallel()
log := logr.New(nil)
{
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err := retry(ctx, log, 1, time.Millisecond, failingFuncFactory(0, errNever))
require.Nil(t, err)
}
{
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err := retry(ctx, log, 2, time.Millisecond, failingFuncFactory(1, errNever))
require.Nil(t, err)
}
{
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err := retry(ctx, log, 2, time.Millisecond, failingFuncFactory(2, errNever))
require.ErrorIs(t, err, errNever)
}
{
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err := retry(ctx, log, 5, time.Millisecond, failingFuncFactory(5, errNever))
require.ErrorIs(t, err, errNever)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment