Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 138 additions & 107 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,144 @@
# retry
# retry-go: Production Retry Logic for Go

[![Release](https://img.shields.io/github/release/codeGROOVE-dev/retry-go.svg?style=flat-square)](https://github.com/codeGROOVE-dev/retry-go/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Go Report Card](https://goreportcard.com/badge/github.com/codeGROOVE-dev/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/codeGROOVE-dev/retry-go)
[![Go Reference](https://pkg.go.dev/badge/github.com/codeGROOVE-dev/retry-go.svg)](https://pkg.go.dev/github.com/codeGROOVE-dev/retry-go)

## Fork Information

This is an actively maintained fork of [avast/retry-go](https://github.com/avast/retry-go), focused on reliability and simplicity. We extend our gratitude to the original authors and contributors at Avast for creating this excellent library.

This fork retains the original v4 API provided by the retry-go codebase, and is a drop-in replacement.

**Key improvements in this fork:**

- Zero dependencies (we removed usage of testify and it's dependencies)
- Enhanced reliability and edge case handling
- Active maintenance and bug fixes
- Adherance to Go best practices, namely:
- https://go.dev/wiki/TestComments
- https://go.dev/wiki/CodeReviewComments
- https://go.dev/doc/effective_go

**Original Project:** [github.com/avast/retry-go](https://github.com/avast/retry-go)

# SYNOPSIS

For full package documentation, see https://pkg.go.dev/github.com/codeGROOVE-dev/retry-go

HTTP GET with retry:

url := "http://example.com"
var body []byte

err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)

if err != nil {
// handle error
}

fmt.Println(string(body))

HTTP GET with retry with data:

url := "http://example.com"

body, err := retry.DoWithData(
func() ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

return body, nil
},
)

if err != nil {
// handle error
}

fmt.Println(string(body))

[More examples](https://github.com/codeGROOVE-dev/retry-go/tree/master/examples)



# SEE ALSO

* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly
complicated interface.

* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for
http calls with retries and backoff

* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the
exponential backoff algorithm from Google's HTTP Client Library for Java. Really
complicated interface.

* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good,
slightly similar as this package, don't have 'simple' `Retry` method

* [matryer/try](https://github.com/matryer/try) - very popular package,
nonintuitive interface (for me)

## Contributing

Contributions are very much welcome.

### Before pull request

please try:
* run tests (`make test`)
* run linter (`make lint`)
* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`)
**Zero dependencies. Memory-bounded. Uptime-focused.**

*Because 99.99% uptime means your retry logic can't be the failure point.*

Actively maintained fork of [avast/retry-go](https://github.com/avast/retry-go) focused on correctness and resource efficiency. 100% API compatible drop-in replacement.

**Key improvements:**
- ⚡ Zero external dependencies
- 🔒 Memory-bounded error accumulation
- 🛡️ Integer overflow protection in backoff
- 🎯 Enhanced readability and debuggability
- 📊 Predictable behavior under load

## Quick Start

### Basic Retry with Error Handling

```go
import (
"net/http"
"github.com/codeGROOVE-dev/retry-go"
)

// Retry API call with exponential backoff + jitter
err := retry.Do(
func() error {
resp, err := http.Get("https://api.stripe.com/v1/charges")
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode >= 500 {
return fmt.Errorf("server error: %d", resp.StatusCode)
}
return nil
},
retry.Attempts(5),
retry.DelayType(retry.CombineDelay(retry.BackOffDelay, retry.RandomDelay)),
)
```

### Retry with Data Return (Generics)

```go
import (
"context"
"encoding/json"
"github.com/codeGROOVE-dev/retry-go"
)

// Database query with timeout and retry
users, err := retry.DoWithData(
func() ([]User, error) {
return db.FindActiveUsers(ctx)
},
retry.Attempts(3),
retry.Context(ctx),
retry.DelayType(retry.BackOffDelay),
)
```

### Production Configuration

```go
// Payment processing with comprehensive retry logic
err := retry.Do(
func() error { return paymentGateway.Charge(ctx, amount) },
retry.Attempts(5),
retry.Context(ctx),
retry.DelayType(retry.FullJitterBackoffDelay),
retry.MaxDelay(30*time.Second),
retry.OnRetry(func(n uint, err error) {
log.Warn("Payment retry", "attempt", n+1, "error", err)
}),
retry.RetryIf(func(err error) bool {
return !isAuthError(err) // Don't retry 4xx errors
}),
)
```

## Key Features

**Unrecoverable Errors** - Stop immediately for certain errors:
```go
if resp.StatusCode == 401 {
return retry.Unrecoverable(errors.New("auth failed"))
}
```

**Context Integration** - Timeout and cancellation:
```go
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
retry.Do(dbQuery, retry.Context(ctx))
```

**Error-Specific Limits** - Different retry counts per error type:
```go
retry.AttemptsForError(1, sql.ErrTxDone) // Don't retry transaction errors
```

## Performance & Scale

| Metric | Value |
|--------|-------|
| Memory overhead | ~200 bytes per operation |
| Error accumulation | Bounded at 1000 errors (DoS protection) |
| Goroutines | Uses calling goroutine only |
| High-throughput safe | No hidden allocations or locks |

## Library Comparison

**[cenkalti/backoff](https://github.com/cenkalti/backoff)** - Complex interface, requires manual retry loops, no error accumulation.

**[sethgrid/pester](https://github.com/sethgrid/pester)** - HTTP-only, lacks general-purpose retry logic.

**[matryer/try](https://github.com/matryer/try)** - Popular but non-standard API, missing production features.

**[rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go)** - Similar design but lacks error-specific limits and comprehensive context handling.

**This fork** builds on avast/retry-go's solid foundation with correctness fixes and resource optimizations.

## Installation

```bash
go get github.com/codeGROOVE-dev/retry-go
```

## Documentation

- [API Docs](https://pkg.go.dev/github.com/codeGROOVE-dev/retry-go)
- [Examples](https://github.com/codeGROOVE-dev/retry-go/tree/master/examples)
- [Tests](https://github.com/codeGROOVE-dev/retry-go/tree/master/retry_test.go)

---

*Production retry logic that just works.*
3 changes: 2 additions & 1 deletion examples/custom_retry_function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
"testing"
"time"

"github.com/codeGROOVE-dev/retry-go")
"github.com/codeGROOVE-dev/retry-go"
)

// RetriableError is a custom error that contains a positive duration for the next retry
type RetriableError struct {
Expand Down
3 changes: 2 additions & 1 deletion examples/delay_based_on_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
"testing"
"time"

"github.com/codeGROOVE-dev/retry-go")
"github.com/codeGROOVE-dev/retry-go"
)

type RetryAfterError struct {
response http.Response
Expand Down
3 changes: 2 additions & 1 deletion examples/errors_history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (
"net/http/httptest"
"testing"

"github.com/codeGROOVE-dev/retry-go")
"github.com/codeGROOVE-dev/retry-go"
)

// TestErrorHistory shows an example of how to get all the previous errors when
// retry.Do ends in success
Expand Down
3 changes: 2 additions & 1 deletion examples/http_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (
"net/http/httptest"
"testing"

"github.com/codeGROOVE-dev/retry-go")
"github.com/codeGROOVE-dev/retry-go"
)

func TestGet(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading