|
1 | | -# retry |
| 1 | +# retry-go: Production Retry Logic for Go |
2 | 2 |
|
3 | 3 | [](https://github.com/codeGROOVE-dev/retry-go/releases/latest) |
4 | 4 | [](LICENSE.md) |
5 | 5 | [](https://goreportcard.com/report/github.com/codeGROOVE-dev/retry-go) |
6 | 6 | [](https://pkg.go.dev/github.com/codeGROOVE-dev/retry-go) |
7 | 7 |
|
8 | | -## Fork Information |
9 | | - |
10 | | -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. |
11 | | - |
12 | | -This fork retains the original v4 API provided by the retry-go codebase, and is a drop-in replacement. |
13 | | - |
14 | | -**Key improvements in this fork:** |
15 | | - |
16 | | -- Zero dependencies (we removed usage of testify and it's dependencies) |
17 | | -- Enhanced reliability and edge case handling |
18 | | -- Active maintenance and bug fixes |
19 | | -- Adherance to Go best practices, namely: |
20 | | - - https://go.dev/wiki/TestComments |
21 | | - - https://go.dev/wiki/CodeReviewComments |
22 | | - - https://go.dev/doc/effective_go |
23 | | - |
24 | | -**Original Project:** [github.com/avast/retry-go](https://github.com/avast/retry-go) |
25 | | - |
26 | | -# SYNOPSIS |
27 | | - |
28 | | -For full package documentation, see https://pkg.go.dev/github.com/codeGROOVE-dev/retry-go |
29 | | - |
30 | | -HTTP GET with retry: |
31 | | - |
32 | | - url := "http://example.com" |
33 | | - var body []byte |
34 | | - |
35 | | - err := retry.Do( |
36 | | - func() error { |
37 | | - resp, err := http.Get(url) |
38 | | - if err != nil { |
39 | | - return err |
40 | | - } |
41 | | - defer resp.Body.Close() |
42 | | - body, err = ioutil.ReadAll(resp.Body) |
43 | | - if err != nil { |
44 | | - return err |
45 | | - } |
46 | | - return nil |
47 | | - }, |
48 | | - ) |
49 | | - |
50 | | - if err != nil { |
51 | | - // handle error |
52 | | - } |
53 | | - |
54 | | - fmt.Println(string(body)) |
55 | | - |
56 | | -HTTP GET with retry with data: |
57 | | - |
58 | | - url := "http://example.com" |
59 | | - |
60 | | - body, err := retry.DoWithData( |
61 | | - func() ([]byte, error) { |
62 | | - resp, err := http.Get(url) |
63 | | - if err != nil { |
64 | | - return nil, err |
65 | | - } |
66 | | - defer resp.Body.Close() |
67 | | - body, err := ioutil.ReadAll(resp.Body) |
68 | | - if err != nil { |
69 | | - return nil, err |
70 | | - } |
71 | | - |
72 | | - return body, nil |
73 | | - }, |
74 | | - ) |
75 | | - |
76 | | - if err != nil { |
77 | | - // handle error |
78 | | - } |
79 | | - |
80 | | - fmt.Println(string(body)) |
81 | | - |
82 | | -[More examples](https://github.com/codeGROOVE-dev/retry-go/tree/master/examples) |
83 | | - |
84 | | - |
85 | | - |
86 | | -# SEE ALSO |
87 | | - |
88 | | -* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly |
89 | | -complicated interface. |
90 | | - |
91 | | -* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for |
92 | | -http calls with retries and backoff |
93 | | - |
94 | | -* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the |
95 | | -exponential backoff algorithm from Google's HTTP Client Library for Java. Really |
96 | | -complicated interface. |
97 | | - |
98 | | -* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good, |
99 | | -slightly similar as this package, don't have 'simple' `Retry` method |
100 | | - |
101 | | -* [matryer/try](https://github.com/matryer/try) - very popular package, |
102 | | -nonintuitive interface (for me) |
103 | | - |
104 | | -## Contributing |
105 | | - |
106 | | -Contributions are very much welcome. |
107 | | - |
108 | | -### Before pull request |
109 | | - |
110 | | -please try: |
111 | | -* run tests (`make test`) |
112 | | -* run linter (`make lint`) |
113 | | -* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`) |
| 8 | +**Zero dependencies. Memory-bounded. Uptime-focused.** |
| 9 | + |
| 10 | +*Because 99.99% uptime means your retry logic can't be the failure point.* |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +**Key improvements:** |
| 15 | +- ⚡ Zero external dependencies |
| 16 | +- 🔒 Memory-bounded error accumulation |
| 17 | +- 🛡️ Integer overflow protection in backoff |
| 18 | +- 🎯 Enhanced readability and debuggability |
| 19 | +- 📊 Predictable behavior under load |
| 20 | + |
| 21 | +## Quick Start |
| 22 | + |
| 23 | +### Basic Retry with Error Handling |
| 24 | + |
| 25 | +```go |
| 26 | +import ( |
| 27 | + "net/http" |
| 28 | + "github.com/codeGROOVE-dev/retry-go" |
| 29 | +) |
| 30 | + |
| 31 | +// Retry API call with exponential backoff + jitter |
| 32 | +err := retry.Do( |
| 33 | + func() error { |
| 34 | + resp, err := http.Get("https://api.stripe.com/v1/charges") |
| 35 | + if err != nil { |
| 36 | + return err |
| 37 | + } |
| 38 | + defer resp.Body.Close() |
| 39 | + |
| 40 | + if resp.StatusCode >= 500 { |
| 41 | + return fmt.Errorf("server error: %d", resp.StatusCode) |
| 42 | + } |
| 43 | + return nil |
| 44 | + }, |
| 45 | + retry.Attempts(5), |
| 46 | + retry.DelayType(retry.CombineDelay(retry.BackOffDelay, retry.RandomDelay)), |
| 47 | +) |
| 48 | +``` |
| 49 | + |
| 50 | +### Retry with Data Return (Generics) |
| 51 | + |
| 52 | +```go |
| 53 | +import ( |
| 54 | + "context" |
| 55 | + "encoding/json" |
| 56 | + "github.com/codeGROOVE-dev/retry-go" |
| 57 | +) |
| 58 | + |
| 59 | +// Database query with timeout and retry |
| 60 | +users, err := retry.DoWithData( |
| 61 | + func() ([]User, error) { |
| 62 | + return db.FindActiveUsers(ctx) |
| 63 | + }, |
| 64 | + retry.Attempts(3), |
| 65 | + retry.Context(ctx), |
| 66 | + retry.DelayType(retry.BackOffDelay), |
| 67 | +) |
| 68 | +``` |
| 69 | + |
| 70 | +### Production Configuration |
| 71 | + |
| 72 | +```go |
| 73 | +// Payment processing with comprehensive retry logic |
| 74 | +err := retry.Do( |
| 75 | + func() error { return paymentGateway.Charge(ctx, amount) }, |
| 76 | + retry.Attempts(5), |
| 77 | + retry.Context(ctx), |
| 78 | + retry.DelayType(retry.FullJitterBackoffDelay), |
| 79 | + retry.MaxDelay(30*time.Second), |
| 80 | + retry.OnRetry(func(n uint, err error) { |
| 81 | + log.Warn("Payment retry", "attempt", n+1, "error", err) |
| 82 | + }), |
| 83 | + retry.RetryIf(func(err error) bool { |
| 84 | + return !isAuthError(err) // Don't retry 4xx errors |
| 85 | + }), |
| 86 | +) |
| 87 | +``` |
| 88 | + |
| 89 | +## Key Features |
| 90 | + |
| 91 | +**Unrecoverable Errors** - Stop immediately for certain errors: |
| 92 | +```go |
| 93 | +if resp.StatusCode == 401 { |
| 94 | + return retry.Unrecoverable(errors.New("auth failed")) |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +**Context Integration** - Timeout and cancellation: |
| 99 | +```go |
| 100 | +ctx, cancel := context.WithTimeout(ctx, 30*time.Second) |
| 101 | +retry.Do(dbQuery, retry.Context(ctx)) |
| 102 | +``` |
| 103 | + |
| 104 | +**Error-Specific Limits** - Different retry counts per error type: |
| 105 | +```go |
| 106 | +retry.AttemptsForError(1, sql.ErrTxDone) // Don't retry transaction errors |
| 107 | +``` |
| 108 | + |
| 109 | +## Performance & Scale |
| 110 | + |
| 111 | +| Metric | Value | |
| 112 | +|--------|-------| |
| 113 | +| Memory overhead | ~200 bytes per operation | |
| 114 | +| Error accumulation | Bounded at 1000 errors (DoS protection) | |
| 115 | +| Goroutines | Uses calling goroutine only | |
| 116 | +| High-throughput safe | No hidden allocations or locks | |
| 117 | + |
| 118 | +## Library Comparison |
| 119 | + |
| 120 | +**[cenkalti/backoff](https://github.com/cenkalti/backoff)** - Complex interface, requires manual retry loops, no error accumulation. |
| 121 | + |
| 122 | +**[sethgrid/pester](https://github.com/sethgrid/pester)** - HTTP-only, lacks general-purpose retry logic. |
| 123 | + |
| 124 | +**[matryer/try](https://github.com/matryer/try)** - Popular but non-standard API, missing production features. |
| 125 | + |
| 126 | +**[rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go)** - Similar design but lacks error-specific limits and comprehensive context handling. |
| 127 | + |
| 128 | +**This fork** builds on avast/retry-go's solid foundation with correctness fixes and resource optimizations. |
| 129 | + |
| 130 | +## Installation |
| 131 | + |
| 132 | +```bash |
| 133 | +go get github.com/codeGROOVE-dev/retry-go |
| 134 | +``` |
| 135 | + |
| 136 | +## Documentation |
| 137 | + |
| 138 | +- [API Docs](https://pkg.go.dev/github.com/codeGROOVE-dev/retry-go) |
| 139 | +- [Examples](https://github.com/codeGROOVE-dev/retry-go/tree/master/examples) |
| 140 | +- [Tests](https://github.com/codeGROOVE-dev/retry-go/tree/master/retry_test.go) |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +*Production retry logic that just works.* |
0 commit comments