-
Notifications
You must be signed in to change notification settings - Fork 48
/
config.go
240 lines (211 loc) · 8.13 KB
/
config.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package circuit
import (
"time"
"github.com/cep21/circuit/v4/faststats"
)
// Config controls how a circuit operates
type Config struct {
General GeneralConfig
Execution ExecutionConfig
Fallback FallbackConfig
Metrics MetricsCollectors
}
// GeneralConfig controls the general logic of the circuit. Things specific to metrics, execution, or fallback are
// in their own configs
type GeneralConfig struct {
// if disabled, Execute functions pass to just calling runFunc and do no tracking or fallbacks
// Note: Java Manager calls this "Enabled". I call it "Disabled" so the zero struct can fill defaults
Disabled bool `json:",omitempty"`
// ForceOpen is https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceopen
ForceOpen bool `json:",omitempty"`
// ForcedClosed is https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceclosed
ForcedClosed bool `json:",omitempty"`
// GoLostErrors can receive errors that would otherwise be lost by `Go` executions. For example, if Go returns
// early but some long time later an error or panic eventually happens.
GoLostErrors func(err error, panics interface{}) `json:"-"`
// ClosedToOpenFactory creates logic that determines if the circuit should go from Closed to Open state.
// By default, it never opens
ClosedToOpenFactory func() ClosedToOpen `json:"-"`
// OpenToClosedFactory creates logic that determines if the circuit should go from Open to Closed state.
// By default, it never closes
OpenToClosedFactory func() OpenToClosed `json:"-"`
// CustomConfig is anything you want.
CustomConfig map[interface{}]interface{} `json:"-"`
// TimeKeeper returns the current way to keep time. You only want to modify this for testing.
TimeKeeper TimeKeeper `json:"-"`
}
// ExecutionConfig is https://github.com/Netflix/Hystrix/wiki/Configuration#execution
type ExecutionConfig struct {
// ExecutionTimeout is https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.thread.timeoutInMilliseconds
Timeout time.Duration
// MaxConcurrentRequests is https://github.com/Netflix/Hystrix/wiki/Configuration#executionisolationsemaphoremaxconcurrentrequests
MaxConcurrentRequests int64
// Normally if the parent context is canceled before a timeout is reached, we don't consider the circuit
// unhealthy. Set this to true to consider those circuits unhealthy.
IgnoreInterrupts bool `json:",omitempty"`
// IsErrInterrupt should return true if the error from the original context should be considered an interrupt error.
// The error passed in will be a non-nil error returned by calling `Err()` on the context passed into Run.
// The default behavior is to consider all errors from the original context interrupt caused errors.
// Default behaviour:
// IsErrInterrupt: function(e err) bool { return true }
IsErrInterrupt func(originalContextError error) bool `json:"-"`
}
// FallbackConfig is https://github.com/Netflix/Hystrix/wiki/Configuration#fallback
type FallbackConfig struct {
// Enabled is opposite of https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerenabled
// Note: Java Manager calls this "Enabled". I call it "Disabled" so the zero struct can fill defaults
Disabled bool `json:",omitempty"`
// MaxConcurrentRequests is https://github.com/Netflix/Hystrix/wiki/Configuration#fallback.isolation.semaphore.maxConcurrentRequests
MaxConcurrentRequests int64
}
// MetricsCollectors can receive metrics during a circuit. They should be fast, as they will
// block circuit operation during function calls.
type MetricsCollectors struct {
Run []RunMetrics `json:"-"`
Fallback []FallbackMetrics `json:"-"`
Circuit []Metrics `json:"-"`
}
// TimeKeeper allows overriding time to test the circuit
type TimeKeeper struct {
// Now should simulate time.Now
Now func() time.Time
// AfterFunc should simulate time.AfterFunc
AfterFunc func(time.Duration, func()) *time.Timer
}
// Configurable is anything that can receive configuration changes while live
type Configurable interface {
// SetConfigThreadSafe can be called while the circuit is currently being used and will modify things that are
// safe to change live.
SetConfigThreadSafe(props Config)
// SetConfigNotThreadSafe should only be called when the circuit is not in use: otherwise it will fail -race
// detection
SetConfigNotThreadSafe(props Config)
}
func (t *TimeKeeper) merge(other TimeKeeper) {
if t.Now == nil {
t.Now = other.Now
}
if t.AfterFunc == nil {
t.AfterFunc = other.AfterFunc
}
}
func (c *ExecutionConfig) merge(other ExecutionConfig) {
if !c.IgnoreInterrupts {
c.IgnoreInterrupts = other.IgnoreInterrupts
}
if c.IsErrInterrupt == nil {
c.IsErrInterrupt = other.IsErrInterrupt
}
if c.MaxConcurrentRequests == 0 {
c.MaxConcurrentRequests = other.MaxConcurrentRequests
}
if c.Timeout == 0 {
c.Timeout = other.Timeout
}
}
func (c *FallbackConfig) merge(other FallbackConfig) {
if c.MaxConcurrentRequests == 0 {
c.MaxConcurrentRequests = other.MaxConcurrentRequests
}
if !c.Disabled {
c.Disabled = other.Disabled
}
}
func (g *GeneralConfig) mergeCustomConfig(other GeneralConfig) {
if len(other.CustomConfig) != 0 {
if g.CustomConfig == nil {
g.CustomConfig = make(map[interface{}]interface{}, len(other.CustomConfig))
}
for k, v := range other.CustomConfig {
if _, exists := g.CustomConfig[k]; !exists {
g.CustomConfig[k] = v
}
}
}
}
func (g *GeneralConfig) merge(other GeneralConfig) {
if g.ClosedToOpenFactory == nil {
g.ClosedToOpenFactory = other.ClosedToOpenFactory
}
if g.OpenToClosedFactory == nil {
g.OpenToClosedFactory = other.OpenToClosedFactory
}
g.mergeCustomConfig(other)
if !g.ForceOpen {
g.ForceOpen = other.ForceOpen
}
if !g.ForcedClosed {
g.ForcedClosed = other.ForcedClosed
}
if !g.Disabled {
g.Disabled = other.Disabled
}
if g.GoLostErrors == nil {
g.GoLostErrors = other.GoLostErrors
}
g.TimeKeeper.merge(other.TimeKeeper)
}
func (m *MetricsCollectors) merge(other MetricsCollectors) {
m.Run = append(m.Run, other.Run...)
m.Fallback = append(m.Fallback, other.Fallback...)
m.Circuit = append(m.Circuit, other.Circuit...)
}
// Merge these properties with another command's properties. Anything set to the zero value, will takes values from
// other.
func (c *Config) Merge(other Config) *Config {
c.Execution.merge(other.Execution)
c.Fallback.merge(other.Fallback)
c.Metrics.merge(other.Metrics)
c.General.merge(other.General)
return c
}
// atomicCircuitConfig is used during circuit operations and allows atomic read/write operations. This lets users
// change config at runtime without requiring locks on common operations
type atomicCircuitConfig struct {
Execution struct {
ExecutionTimeout faststats.AtomicInt64
MaxConcurrentRequests faststats.AtomicInt64
}
Fallback struct {
Disabled faststats.AtomicBoolean
MaxConcurrentRequests faststats.AtomicInt64
}
CircuitBreaker struct {
ForceOpen faststats.AtomicBoolean
ForcedClosed faststats.AtomicBoolean
Disabled faststats.AtomicBoolean
}
GoSpecific struct {
IgnoreInterrupts faststats.AtomicBoolean
}
}
func (a *atomicCircuitConfig) reset(config Config) {
a.CircuitBreaker.ForcedClosed.Set(config.General.ForcedClosed)
a.CircuitBreaker.ForceOpen.Set(config.General.ForceOpen)
a.CircuitBreaker.Disabled.Set(config.General.Disabled)
a.Execution.ExecutionTimeout.Set(config.Execution.Timeout.Nanoseconds())
a.Execution.MaxConcurrentRequests.Set(config.Execution.MaxConcurrentRequests)
a.GoSpecific.IgnoreInterrupts.Set(config.Execution.IgnoreInterrupts)
a.Fallback.Disabled.Set(config.Fallback.Disabled)
a.Fallback.MaxConcurrentRequests.Set(config.Fallback.MaxConcurrentRequests)
}
var defaultExecutionConfig = ExecutionConfig{
Timeout: time.Second,
MaxConcurrentRequests: 10,
}
var defaultFallbackConfig = FallbackConfig{
MaxConcurrentRequests: 10,
}
var defaultGoSpecificConfig = GeneralConfig{
ClosedToOpenFactory: neverOpensFactory,
OpenToClosedFactory: neverClosesFactory,
TimeKeeper: TimeKeeper{
Now: time.Now,
AfterFunc: time.AfterFunc,
},
}
var defaultCommandProperties = Config{
Execution: defaultExecutionConfig,
Fallback: defaultFallbackConfig,
General: defaultGoSpecificConfig,
}