/
invoker.go
100 lines (85 loc) · 2.58 KB
/
invoker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package retry
import (
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/multi"
"github.com/hyperledger/fabric-sdk-go/pkg/common/logging"
)
var logger = logging.NewLogger("fabsdk/common")
// Invocation is the function to be invoked.
type Invocation func() (interface{}, error)
// BeforeRetryHandler is a function that's invoked before
// a retry attempt.
type BeforeRetryHandler func(error)
// RetryableInvoker manages invocations that could return
// errors and retries the invocation on transient errors.
type RetryableInvoker struct {
handler Handler
beforeRetry BeforeRetryHandler
}
// InvokerOpt is an invoker option
type InvokerOpt func(invoker *RetryableInvoker)
// WithBeforeRetry specifies a function to call before a retry attempt
func WithBeforeRetry(beforeRetry BeforeRetryHandler) InvokerOpt {
return func(invoker *RetryableInvoker) {
invoker.beforeRetry = beforeRetry
}
}
// NewInvoker creates a new RetryableInvoker
func NewInvoker(handler Handler, opts ...InvokerOpt) *RetryableInvoker {
invoker := &RetryableInvoker{
handler: handler,
}
for _, opt := range opts {
opt(invoker)
}
return invoker
}
// Invoke invokes the given function and performs retries according
// to the retry options.
func (ri *RetryableInvoker) Invoke(invocation Invocation) (interface{}, error) {
attemptNum := 0
var lastErr error
for {
attemptNum++
if attemptNum > 1 {
logger.Debugf("Retry attempt #%d on error [%s]", attemptNum, lastErr)
}
retval, err := invocation()
if err == nil {
if attemptNum > 1 {
logger.Debugf("Success on attempt #%d after error [%s]", attemptNum, lastErr)
}
return retval, nil
}
logger.Debugf("Failed with err [%s] on attempt #%d. Checking if retry is warranted...", err, attemptNum)
if !ri.resolveRetry(err) {
if lastErr != nil && lastErr.Error() != err.Error() {
logger.Debugf("... retry for err [%s] is NOT warranted after %d attempt(s). Previous error [%s]", err, attemptNum, lastErr)
} else {
logger.Debugf("... retry for err [%s] is NOT warranted after %d attempt(s).", err, attemptNum)
}
return nil, err
}
logger.Debugf("... retry for err [%s] is warranted", err)
lastErr = err
}
}
func (ri *RetryableInvoker) resolveRetry(err error) bool {
errs, ok := err.(multi.Errors)
if !ok {
errs = append(errs, err)
}
for _, e := range errs {
if ri.handler.Required(e) {
logger.Debugf("Retrying on error %s", e)
if ri.beforeRetry != nil {
ri.beforeRetry(err)
}
return true
}
}
return false
}