Codebase list golang-github-go-kit-kit / 4c9ff19
Encode max retry logic in callback. Ross McFarlane 7 years ago
2 changed file(s) with 17 addition(s) and 29 deletion(s). Raw diff Collapse all Expand all
99 "github.com/go-kit/kit/endpoint"
1010 )
1111
12 // Callback is a function that indicates the current attempt count and the error
12 // Callback is a function that is given the current attempt count and the error
1313 // encountered. Should return whether the Retry function should continue trying,
1414 // and a custom error message if desired. The error message may be nil, but a
1515 // true/false is always expected. In all cases if the error message is supplied,
2222 // balancer. Requests that return errors will be retried until they succeed,
2323 // up to max times, or until the timeout is elapsed, whichever comes first.
2424 func Retry(max int, timeout time.Duration, b Balancer) endpoint.Endpoint {
25 return RetryWithCallback(max, timeout, b, func(c int, err error) (bool, error) { return true, nil })
25 return RetryWithCallback(timeout, b, maxRetries(max))
26 }
27
28 // maxRetries returns a callback function that enforces max retries.
29 func maxRetries(max int) Callback {
30 return func(n int, err error) (bool, error) {
31 if n < max {
32 return true, nil
33 }
34 return false, nil
35 }
2636 }
2737
2838 // RetryWithCallback wraps a service load balancer and returns an endpoint oriented load
3141 // balancer. Requests that return errors will be retried until they succeed,
3242 // up to max times, until the callback returns false, or until the timeout is elapsed,
3343 // whichever comes first.
34 func RetryWithCallback(max int, timeout time.Duration, b Balancer, cb Callback) endpoint.Endpoint {
44 func RetryWithCallback(timeout time.Duration, b Balancer, cb Callback) endpoint.Endpoint {
3545 if cb == nil {
3646 panic("nil Callback")
3747 }
4656 a = []string{}
4757 )
4858 defer cancel()
49 for i := 1; i <= max; i++ {
59 for i := 1; ; i++ {
5060 go func() {
5161 e, err := b.Endpoint()
5262 if err != nil {
8292 continue
8393 }
8494 }
85 return nil, fmt.Errorf("retry attempts exceeded (%s)", strings.Join(a, "; "))
8695 }
8796 }
4141 ctx = context.Background()
4242 )
4343 if _, err := loadbalancer.Retry(retries, time.Second, lb)(ctx, struct{}{}); err == nil {
44 t.Errorf("expected error, got none")
44 t.Errorf("expected error two, got none")
4545 }
4646 }
4747
9797 }
9898 endpoints = sd.FixedSubscriber{} // no endpoints
9999 lb = loadbalancer.NewRoundRobin(endpoints)
100 retry = loadbalancer.RetryWithCallback(999, time.Second, lb, cb) // lots of retries
100 retry = loadbalancer.RetryWithCallback(time.Second, lb, cb) // lots of retries
101101 ctx = context.Background()
102102 )
103103 _, err := retry(ctx, struct{}{})
106106 }
107107 if err.Error() != "Aborting early" {
108108 t.Errorf("expected custom error message, got %v", err)
109 }
110 }
111
112 func TestAbortEarlyOnNTries_WCB(t *testing.T) {
113 var (
114 cb = func(count int, err error) (bool, error) {
115 if count >= 4 {
116 t.Errorf("expected retries to abort at 3 but continued to %v", count)
117 }
118 if count == 3 {
119 return false, nil
120 }
121 return true, nil
122 }
123 endpoints = sd.FixedSubscriber{} // no endpoints
124 lb = loadbalancer.NewRoundRobin(endpoints)
125 retry = loadbalancer.RetryWithCallback(999, time.Second, lb, cb) // lots of retries
126 ctx = context.Background()
127 )
128 if _, err := retry(ctx, struct{}{}); err == nil {
129 t.Errorf("expected error, got none") // should fail
130109 }
131110 }
132111
144123 }
145124 endpoints = sd.FixedSubscriber{endpoint} // no endpoints
146125 lb = loadbalancer.NewRoundRobin(endpoints)
147 retry = loadbalancer.RetryWithCallback(999, time.Second, lb, cb) // lots of retries
126 retry = loadbalancer.RetryWithCallback(time.Second, lb, cb) // lots of retries
148127 ctx = context.Background()
149128 )
150129 _, _ = retry(ctx, struct{}{})