-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add v2 implementation of repeater with strategies and tests - Implement retry strategies: FixedDelay and Backoff with types (Constant, Linear, Exponential) and options for max delay and jitter. - Add Repeater struct to handle retry logic, providing NewBackoff and NewFixed constructor functions. - Include error handling with immediate termination on specified critical errors, including ErrAny for stopping on any error. - Integrate full testing suite to confirm correct behavior of retry mechanisms, including context cancellation respects. - Update README with usage examples and detailed explanations of strategies and options. - Configure GitHub Actions CI for building and testing Go v1.24 codebase with coverage reporting. * lint: separate linter config and few minor warns * add ref to v2 to readme * drop v2 as we pre-v1
- Loading branch information
Showing
13 changed files
with
658 additions
and
513 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,135 @@ | ||
# Repeater [](https://github.com/go-pkgz/repeater/actions) [](https://goreportcard.com/report/github.com/go-pkgz/repeater) [](https://coveralls.io/github/go-pkgz/repeater?branch=master) | ||
# Repeater | ||
|
||
Repeater calls a function until it returns no error, up to some number of iterations and delays defined by strategy. It terminates immediately on err from the provided (optional) list of critical errors. | ||
[](https://github.com/go-pkgz/repeater/actions) [](https://goreportcard.com/report/github.com/go-pkgz/repeater) [](https://coveralls.io/github/go-pkgz/repeater?branch=master) | ||
|
||
Package repeater implements a functional mechanism to repeat operations with different retry strategies. | ||
|
||
## Install and update | ||
|
||
`go get -u github.com/go-pkgz/repeater` | ||
|
||
## How to use | ||
## Usage | ||
|
||
### Basic Example with Exponential Backoff | ||
|
||
```go | ||
// create repeater with exponential backoff | ||
r := repeater.NewBackoff(5, time.Second) // 5 attempts starting with 1s delay | ||
|
||
err := r.Do(ctx, func() error { | ||
// do something that may fail | ||
return nil | ||
}) | ||
``` | ||
|
||
New Repeater created by `New(strtg strategy.Interface)` or shortcut for default - `NewDefault(repeats int, delay time.Duration) *Repeater`. | ||
### Fixed Delay with Critical Error | ||
|
||
To activate invoke `Do` method. `Do` repeats func until no error returned. Predefined (optional) errors terminate the loop immediately. | ||
`func (r Repeater) Do(ctx context.Context, fun func() error, errors ...error) (err error)` | ||
```go | ||
// create repeater with fixed delay | ||
r := repeater.NewFixed(3, 100*time.Millisecond) | ||
|
||
### Repeating strategy | ||
criticalErr := errors.New("critical error") | ||
|
||
User can provide his own strategy implementing the interface: | ||
err := r.Do(ctx, func() error { | ||
// do something that may fail | ||
return fmt.Errorf("temp error") | ||
}, criticalErr) // will stop immediately if criticalErr returned | ||
``` | ||
|
||
### Custom Backoff Strategy | ||
|
||
```go | ||
type Interface interface { | ||
Start(ctx context.Context) chan struct{} | ||
r := repeater.NewBackoff(5, time.Second, | ||
repeater.WithMaxDelay(10*time.Second), | ||
repeater.WithBackoffType(repeater.BackoffLinear), | ||
repeater.WithJitter(0.1), | ||
) | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
defer cancel() | ||
|
||
err := r.Do(ctx, func() error { | ||
// do something that may fail | ||
return nil | ||
}) | ||
``` | ||
|
||
### Stop on Any Error | ||
|
||
```go | ||
r := repeater.NewFixed(3, time.Millisecond) | ||
|
||
err := r.Do(ctx, func() error { | ||
return errors.New("some error") | ||
}, repeater.ErrAny) // will stop on any error | ||
``` | ||
|
||
## Strategies | ||
|
||
The package provides several retry strategies: | ||
|
||
1. **Fixed Delay** - each retry happens after a fixed time interval | ||
2. **Backoff** - delay between retries increases according to the chosen algorithm: | ||
- Constant - same delay between attempts | ||
- Linear - delay increases linearly | ||
- Exponential - delay doubles with each attempt | ||
|
||
Backoff strategy can be customized with: | ||
- Maximum delay cap | ||
- Jitter to prevent thundering herd | ||
- Different backoff types (constant/linear/exponential) | ||
|
||
### Custom Strategies | ||
|
||
You can implement your own retry strategy by implementing the Strategy interface: | ||
|
||
```go | ||
type Strategy interface { | ||
// NextDelay returns delay for the next attempt | ||
// attempt starts from 1 | ||
NextDelay(attempt int) time.Duration | ||
} | ||
``` | ||
|
||
Example of a custom strategy that increases delay by a custom factor: | ||
|
||
```go | ||
// CustomStrategy implements Strategy with custom factor-based delays | ||
type CustomStrategy struct { | ||
Initial time.Duration | ||
Factor float64 | ||
} | ||
|
||
func (s CustomStrategy) NextDelay(attempt int) time.Duration { | ||
if attempt <= 0 { | ||
return 0 | ||
} | ||
delay := time.Duration(float64(s.Initial) * math.Pow(s.Factor, float64(attempt-1))) | ||
return delay | ||
} | ||
|
||
// Usage | ||
strategy := &CustomStrategy{Initial: time.Second, Factor: 1.5} | ||
r := repeater.NewWithStrategy(5, strategy) | ||
err := r.Do(ctx, func() error { | ||
// attempts will be delayed by: 1s, 1.5s, 2.25s, 3.37s, 5.06s | ||
return nil | ||
}) | ||
``` | ||
|
||
Returned channels used as "ticks," i.e., for each repeat or initial operation one read from this channel needed. Closing the channel indicates "done with retries." It is pretty much the same idea as `time.Timer` or `time.Tick` implements. Note - the first (technically not-repeated-yet) call won't happen **until something sent to the channel**. For this reason, the typical strategy sends the first "tick" before the first wait/sleep. | ||
## Options | ||
|
||
For backoff strategy, several options are available: | ||
|
||
```go | ||
WithMaxDelay(time.Duration) // set maximum delay between retries | ||
WithBackoffType(BackoffType) // set backoff type (constant/linear/exponential) | ||
WithJitter(float64) // add randomness to delays (0-1.0) | ||
``` | ||
|
||
Three strategies provided byt the package: | ||
## Error Handling | ||
|
||
1. **Fixed delay**, up to max number of attempts. It is the default strategy used by `repeater.NewDefault` constructor. | ||
2. **BackOff** with jitter provides an exponential backoff. It starts from `Duration` interval and goes in steps with `last * math.Pow(factor, attempt)`. Optional jitter randomizes intervals a little. _Factor = 1 effectively makes this strategy fixed with `Duration` delay._ | ||
3. **Once** strategy does not do any repeats and mainly used for tests/mocks`. | ||
- Stops on context cancellation | ||
- Can stop on specific errors (pass them as additional parameters to Do) | ||
- Special `ErrAny` to stop on any error | ||
- Returns last error if all attempts fail |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | ||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
Oops, something went wrong.