New upstream release.
Debian Janitor
2 years ago
0 | # Changelog | |
1 | ||
2 | #### Version 1.2.0 (2019-06-14) | |
3 | ||
4 | *Note: This release requires Golang at least 1.7, which is higher than the | |
5 | previous release. All the versions being dropped are multiple years old and no | |
6 | longer supported upstream, so I'm not counting this as a breaking change.* | |
7 | ||
8 | - Add `RunCtx` method on `Retrier` to support running with a context. | |
9 | - Ensure the `Retrier`'s use of random numbers is concurrency-safe. | |
10 | - Bump CI to ensure we support newer Golang versions. | |
11 | ||
12 | #### Version 1.1.0 (2018-03-26) | |
13 | ||
14 | - Improve documentation and fix some typos. | |
15 | - Bump CI to ensure we support newer Golang versions. | |
16 | - Add `IsEmpty()` method on `Semaphore`. | |
17 | ||
18 | #### Version 1.0.0 (2015-02-13) | |
19 | ||
20 | Initial release. |
2 | 2 | |
3 | 3 | [![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency) |
4 | 4 | [![GoDoc](https://godoc.org/github.com/eapache/go-resiliency?status.svg)](https://godoc.org/github.com/eapache/go-resiliency) |
5 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) | |
5 | 6 | |
6 | 7 | Resiliency patterns for golang. |
7 | 8 | Based in part on [Hystrix](https://github.com/Netflix/Hystrix), |
2 | 2 | |
3 | 3 | [![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency) |
4 | 4 | [![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/batcher?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/batcher) |
5 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) | |
5 | 6 | |
6 | 7 | The batching resiliency pattern for golang. |
7 | 8 |
2 | 2 | |
3 | 3 | [![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency) |
4 | 4 | [![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/breaker?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/breaker) |
5 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) | |
5 | 6 | |
6 | 7 | The circuit-breaker resiliency pattern for golang. |
7 | 8 |
2 | 2 | |
3 | 3 | [![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency) |
4 | 4 | [![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/deadline?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/deadline) |
5 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) | |
5 | 6 | |
6 | 7 | The deadline/timeout resiliency pattern for golang. |
7 | 8 | |
11 | 12 | dl := deadline.New(1 * time.Second) |
12 | 13 | |
13 | 14 | err := dl.Run(func(stopper <-chan struct{}) error { |
14 | // do something possibly slow | |
15 | // check stopper function and give up if timed out | |
15 | // do something potentially slow | |
16 | // give up when the `stopper` channel is closed (indicating a time-out) | |
16 | 17 | return nil |
17 | 18 | }) |
18 | 19 |
0 | golang-gopkg-eapache-go-resiliency.v1 (1.2.0-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Fri, 28 May 2021 11:54:16 -0000 | |
5 | ||
0 | 6 | golang-gopkg-eapache-go-resiliency.v1 (1.0.0-6) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Debian Janitor ] |
2 | 2 | |
3 | 3 | [![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency) |
4 | 4 | [![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/retrier?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/retrier) |
5 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) | |
5 | 6 | |
6 | 7 | The retriable resiliency pattern for golang. |
7 | 8 |
1 | 1 | package retrier |
2 | 2 | |
3 | 3 | import ( |
4 | "context" | |
4 | 5 | "math/rand" |
6 | "sync" | |
5 | 7 | "time" |
6 | 8 | ) |
7 | 9 | |
12 | 14 | class Classifier |
13 | 15 | jitter float64 |
14 | 16 | rand *rand.Rand |
17 | randMu sync.Mutex | |
15 | 18 | } |
16 | 19 | |
17 | 20 | // New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern |
30 | 33 | } |
31 | 34 | } |
32 | 35 | |
33 | // Run executes the given work function, then classifies its return value based on the classifier used | |
36 | // Run executes the given work function by executing RunCtx without context.Context. | |
37 | func (r *Retrier) Run(work func() error) error { | |
38 | return r.RunCtx(context.Background(), func(ctx context.Context) error { | |
39 | // never use ctx | |
40 | return work() | |
41 | }) | |
42 | } | |
43 | ||
44 | // RunCtx executes the given work function, then classifies its return value based on the classifier used | |
34 | 45 | // to construct the Retrier. If the result is Succeed or Fail, the return value of the work function is |
35 | 46 | // returned to the caller. If the result is Retry, then Run sleeps according to the its backoff policy |
36 | 47 | // before retrying. If the total number of retries is exceeded then the return value of the work function |
37 | 48 | // is returned to the caller regardless. |
38 | func (r *Retrier) Run(work func() error) error { | |
49 | func (r *Retrier) RunCtx(ctx context.Context, work func(ctx context.Context) error) error { | |
39 | 50 | retries := 0 |
40 | 51 | for { |
41 | ret := work() | |
52 | ret := work(ctx) | |
42 | 53 | |
43 | 54 | switch r.class.Classify(ret) { |
44 | 55 | case Succeed, Fail: |
47 | 58 | if retries >= len(r.backoff) { |
48 | 59 | return ret |
49 | 60 | } |
50 | time.Sleep(r.calcSleep(retries)) | |
61 | ||
62 | timeout := time.After(r.calcSleep(retries)) | |
63 | if err := r.sleep(ctx, timeout); err != nil { | |
64 | return err | |
65 | } | |
66 | ||
51 | 67 | retries++ |
52 | 68 | } |
53 | 69 | } |
54 | 70 | } |
55 | 71 | |
72 | func (r *Retrier) sleep(ctx context.Context, t <-chan time.Time) error { | |
73 | select { | |
74 | case <-t: | |
75 | return nil | |
76 | case <-ctx.Done(): | |
77 | return ctx.Err() | |
78 | } | |
79 | } | |
80 | ||
56 | 81 | func (r *Retrier) calcSleep(i int) time.Duration { |
82 | // lock unsafe rand prng | |
83 | r.randMu.Lock() | |
84 | defer r.randMu.Unlock() | |
57 | 85 | // take a random float in the range (-r.jitter, +r.jitter) and multiply it by the base amount |
58 | 86 | return r.backoff[i] + time.Duration(((r.rand.Float64()*2)-1)*r.jitter*float64(r.backoff[i])) |
59 | 87 | } |
0 | 0 | package retrier |
1 | 1 | |
2 | 2 | import ( |
3 | "context" | |
4 | "errors" | |
3 | 5 | "testing" |
4 | 6 | "time" |
5 | 7 | ) |
14 | 16 | return nil |
15 | 17 | } |
16 | 18 | return returns[i-1] |
19 | } | |
20 | } | |
21 | ||
22 | func genWorkWithCtx() func(ctx context.Context) error { | |
23 | i = 0 | |
24 | return func(ctx context.Context) error { | |
25 | select { | |
26 | case <-ctx.Done(): | |
27 | return errFoo | |
28 | default: | |
29 | i++ | |
30 | } | |
31 | return nil | |
17 | 32 | } |
18 | 33 | } |
19 | 34 | |
41 | 56 | t.Error(err) |
42 | 57 | } |
43 | 58 | if i != 1 { |
59 | t.Error("run wrong number of times") | |
60 | } | |
61 | } | |
62 | ||
63 | func TestRetrierCtx(t *testing.T) { | |
64 | ctx, cancel := context.WithCancel(context.Background()) | |
65 | ||
66 | r := New([]time.Duration{0, 10 * time.Millisecond}, WhitelistClassifier{}) | |
67 | ||
68 | err := r.RunCtx(ctx, genWorkWithCtx()) | |
69 | if err != nil { | |
70 | t.Error(err) | |
71 | } | |
72 | if i != 1 { | |
73 | t.Error("run wrong number of times") | |
74 | } | |
75 | ||
76 | cancel() | |
77 | ||
78 | err = r.RunCtx(ctx, genWorkWithCtx()) | |
79 | if err != errFoo { | |
80 | t.Error("context must be cancelled") | |
81 | } | |
82 | if i != 0 { | |
44 | 83 | t.Error("run wrong number of times") |
45 | 84 | } |
46 | 85 | } |
114 | 153 | } |
115 | 154 | } |
116 | 155 | |
156 | func TestRetrierThreadSafety(t *testing.T) { | |
157 | r := New([]time.Duration{0}, nil) | |
158 | for i := 0; i < 2; i++ { | |
159 | go func() { | |
160 | r.Run(func() error { | |
161 | return errors.New("error") | |
162 | }) | |
163 | }() | |
164 | } | |
165 | } | |
166 | ||
117 | 167 | func ExampleRetrier() { |
118 | 168 | r := New(ConstantBackoff(3, 100*time.Millisecond), nil) |
119 | 169 |
2 | 2 | |
3 | 3 | [![Build Status](https://travis-ci.org/eapache/go-resiliency.svg?branch=master)](https://travis-ci.org/eapache/go-resiliency) |
4 | 4 | [![GoDoc](https://godoc.org/github.com/eapache/go-resiliency/semaphore?status.svg)](https://godoc.org/github.com/eapache/go-resiliency/semaphore) |
5 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) | |
5 | 6 | |
6 | 7 | The semaphore resiliency pattern for golang. |
7 | 8 |
7 | 7 | |
8 | 8 | // ErrNoTickets is the error returned by Acquire when it could not acquire |
9 | 9 | // a ticket from the semaphore within the configured timeout. |
10 | var ErrNoTickets = errors.New("could not aquire semaphore ticket") | |
10 | var ErrNoTickets = errors.New("could not acquire semaphore ticket") | |
11 | 11 | |
12 | 12 | // Semaphore implements the semaphore resiliency pattern |
13 | 13 | type Semaphore struct { |
42 | 42 | func (s *Semaphore) Release() { |
43 | 43 | <-s.sem |
44 | 44 | } |
45 | ||
46 | // IsEmpty will return true if no tickets are being held at that instant. | |
47 | // It is safe to call concurrently with Acquire and Release, though do note | |
48 | // that the result may then be unpredictable. | |
49 | func (s *Semaphore) IsEmpty() bool { | |
50 | return len(s.sem) == 0 | |
51 | } |
44 | 44 | } |
45 | 45 | } |
46 | 46 | |
47 | func TestSemaphoreEmpty(t *testing.T) { | |
48 | sem := New(2, 200*time.Millisecond) | |
49 | ||
50 | if !sem.IsEmpty() { | |
51 | t.Error("semaphore should be empty") | |
52 | } | |
53 | ||
54 | sem.Acquire() | |
55 | ||
56 | if sem.IsEmpty() { | |
57 | t.Error("semaphore should not be empty") | |
58 | } | |
59 | ||
60 | sem.Release() | |
61 | ||
62 | if !sem.IsEmpty() { | |
63 | t.Error("semaphore should be empty") | |
64 | } | |
65 | } | |
66 | ||
47 | 67 | func ExampleSemaphore() { |
48 | 68 | sem := New(3, 1*time.Second) |
49 | 69 |