Codebase list golang-gopkg-eapache-go-resiliency.v1 / 7304939
Implement breaker.Go() Properly supports launching breaker-protected goroutines. Evan Huus 9 years ago
2 changed file(s) with 109 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
8080 return result
8181 }
8282
83 // Go will either return ErrBreakerOpen immediately if the circuit-breaker is
84 // already open, or it will run the given function in a separate goroutine.
85 // If the function is run, Go will return nil immediately, and will *not* return
86 // the return value of the function. It is safe to call Go concurrently on the
87 // same Breaker.
88 func (b *Breaker) Go(x func() error) error {
89 b.lock.RLock()
90 state := b.state
91 b.lock.RUnlock()
92
93 if state == open {
94 return ErrBreakerOpen
95 }
96
97 go func() {
98 var panicValue interface{}
99
100 result := func() error {
101 defer func() {
102 panicValue = recover()
103 }()
104 return x()
105 }()
106
107 if result == nil && panicValue == nil && state == closed {
108 // short-circuit the normal, success path without
109 // contending on the lock
110 return
111 }
112
113 b.processResult(result, panicValue)
114
115 if panicValue != nil {
116 // as close as Go lets us come to a "rethrow" although
117 // unfortunately we lose the original panicing location
118 panic(panicValue)
119 }
120 }()
121
122 return nil
123 }
124
83125 func (b *Breaker) processResult(result error, panicValue interface{}) {
84126 b.lock.Lock()
85127 defer b.lock.Unlock()
1818 func TestBreakerErrorExpiry(t *testing.T) {
1919 breaker := New(2, 1, 1*time.Second)
2020
21 for i := 0; i < 5; i++ {
21 for i := 0; i < 3; i++ {
2222 if err := breaker.Run(returnsError); err != errSomeError {
23 t.Error(err)
24 }
25 time.Sleep(1 * time.Second)
26 }
27
28 for i := 0; i < 3; i++ {
29 if err := breaker.Go(returnsError); err != nil {
2330 t.Error(err)
2431 }
2532 time.Sleep(1 * time.Second)
7683 }
7784 }
7885
86 func TestBreakerAsyncStateTransitions(t *testing.T) {
87 breaker := New(3, 2, 1*time.Second)
88
89 // three errors opens the breaker
90 for i := 0; i < 3; i++ {
91 if err := breaker.Go(returnsError); err != nil {
92 t.Error(err)
93 }
94 }
95
96 // just enough to yield the scheduler and let the goroutines work off
97 time.Sleep(1 * time.Millisecond)
98
99 // breaker is open
100 for i := 0; i < 5; i++ {
101 if err := breaker.Go(returnsError); err != ErrBreakerOpen {
102 t.Error(err)
103 }
104 }
105
106 // wait for it to half-close
107 time.Sleep(2 * time.Second)
108 // one success works, but is not enough to fully close
109 if err := breaker.Go(returnsSuccess); err != nil {
110 t.Error(err)
111 }
112 // error works, but re-opens immediately
113 if err := breaker.Go(returnsError); err != nil {
114 t.Error(err)
115 }
116 // just enough to yield the scheduler and let the goroutines work off
117 time.Sleep(1 * time.Millisecond)
118 // breaker is open
119 if err := breaker.Go(returnsError); err != ErrBreakerOpen {
120 t.Error(err)
121 }
122
123 // wait for it to half-close
124 time.Sleep(2 * time.Second)
125 // two successes is enough to close it for good
126 for i := 0; i < 2; i++ {
127 if err := breaker.Go(returnsSuccess); err != nil {
128 t.Error(err)
129 }
130 }
131 // just enough to yield the scheduler and let the goroutines work off
132 time.Sleep(1 * time.Millisecond)
133 // error works
134 if err := breaker.Go(returnsError); err != nil {
135 t.Error(err)
136 }
137 // just enough to yield the scheduler and let the goroutines work off
138 time.Sleep(1 * time.Millisecond)
139 // breaker is still closed
140 if err := breaker.Go(returnsSuccess); err != nil {
141 t.Error(err)
142 }
143 }
144
79145 func ExampleBreaker() {
80146 breaker := New(3, 1, 5*time.Second)
81147