-
Notifications
You must be signed in to change notification settings - Fork 50
/
executor_backoff.go
110 lines (85 loc) · 3.09 KB
/
executor_backoff.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
101
102
103
104
105
106
107
108
109
110
package exec
import (
"context"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/justtrackio/gosoline/pkg/log"
"github.com/justtrackio/gosoline/pkg/uuid"
)
type BackoffExecutor struct {
logger log.Logger
uuidGen uuid.Uuid
resource *ExecutableResource
checks []ErrorChecker
settings *BackoffSettings
}
func NewBackoffExecutor(logger log.Logger, res *ExecutableResource, settings *BackoffSettings, checks ...ErrorChecker) *BackoffExecutor {
return &BackoffExecutor{
logger: logger,
uuidGen: uuid.New(),
resource: res,
checks: checks,
settings: settings,
}
}
func (e *BackoffExecutor) Execute(ctx context.Context, f Executable) (interface{}, error) {
logger := e.logger.WithContext(ctx).WithFields(log.Fields{
"exec_id": e.uuidGen.NewV4(),
"exec_resource_type": e.resource.Type,
"exec_resource_name": e.resource.Name,
})
delayedCtx, stop := WithDelayedCancelContext(ctx, e.settings.CancelDelay)
defer stop()
var res interface{}
var err error
var errType ErrorType
backoffConfig := NewExponentialBackOff(e.settings)
backoffCtx := backoff.WithContext(backoffConfig, ctx)
attempts := 1
start := time.Now()
notify := func(err error, _ time.Duration) {
logger.Warn("retrying resource %s after error: %s", e.resource, err.Error())
attempts++
}
_ = backoff.RetryNotify(func() error {
res, err = f(delayedCtx)
if err == nil {
return nil
}
if e.settings.MaxAttempts > 0 && attempts >= e.settings.MaxAttempts {
return backoff.Permanent(err)
}
for _, check := range e.checks {
errType = check(res, err)
switch errType {
case ErrorTypeOk:
return nil
case ErrorTypeRetryable:
return err
case ErrorTypePermanent:
return backoff.Permanent(err)
}
}
return backoff.Permanent(err)
}, backoffCtx, notify)
duration := time.Since(start)
// we're having an error after reaching the MaxAttempts and the error isn't good-natured
if err != nil && errType != ErrorTypeOk && e.settings.MaxAttempts > 0 && attempts > e.settings.MaxAttempts {
logger.Warn("crossed max attempts with an error on requesting resource %s after %d attempts in %s: %s", e.resource, attempts, duration, err.Error())
return res, NewErrAttemptsExceeded(e.resource, attempts, duration, err)
}
// we're having an error after reaching the MaxElapsedTime and the error isn't good-natured
if err != nil && errType != ErrorTypeOk && e.settings.MaxElapsedTime > 0 && duration > e.settings.MaxElapsedTime {
logger.Warn("crossed max elapsed time with an error on requesting resource %s after %d attempts in %s: %s", e.resource, attempts, duration, err.Error())
return res, NewErrMaxElapsedTimeExceeded(e.resource, attempts, duration, e.settings.MaxElapsedTime, err)
}
// we're still having an error and the error isn't good-natured
if err != nil && errType != ErrorTypeOk {
logger.Warn("error on requesting resource %s after %d attempts in %s: %s", e.resource, attempts, duration, err.Error())
return res, err
}
if attempts > 1 {
logger.Info("sent request to resource %s successful after %d attempts in %s", e.resource, attempts, duration)
}
return res, err
}