Skip to content

Commit af130d0

Browse files
committed
Fix bugs of outlier unit tests
1 parent dd1b92f commit af130d0

File tree

17 files changed

+447
-211
lines changed

17 files changed

+447
-211
lines changed

api/slot_chain.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"github.com/alibaba/sentinel-golang/core/hotspot"
2222
"github.com/alibaba/sentinel-golang/core/isolation"
2323
"github.com/alibaba/sentinel-golang/core/log"
24-
"github.com/alibaba/sentinel-golang/core/outlier"
2524
"github.com/alibaba/sentinel-golang/core/stat"
2625
"github.com/alibaba/sentinel-golang/core/system"
2726
)
@@ -41,13 +40,11 @@ func BuildDefaultSlotChain() *base.SlotChain {
4140
sc.AddRuleCheckSlot(isolation.DefaultSlot)
4241
sc.AddRuleCheckSlot(hotspot.DefaultSlot)
4342
sc.AddRuleCheckSlot(circuitbreaker.DefaultSlot)
44-
sc.AddRuleCheckSlot(outlier.DefaultSlot)
4543

4644
sc.AddStatSlot(stat.DefaultSlot)
4745
sc.AddStatSlot(log.DefaultSlot)
4846
sc.AddStatSlot(flow.DefaultStandaloneStatSlot)
4947
sc.AddStatSlot(hotspot.DefaultConcurrencyStatSlot)
5048
sc.AddStatSlot(circuitbreaker.DefaultMetricStatSlot)
51-
sc.AddStatSlot(outlier.DefaultMetricStatSlot)
5249
return sc
5350
}

core/outlier/recycler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package outlier
1616

1717
import (
1818
"errors"
19+
"fmt"
1920
"sync"
2021
"time"
2122

@@ -38,6 +39,11 @@ type task struct {
3839

3940
func init() {
4041
go func() {
42+
defer func() {
43+
if err := recover(); err != nil {
44+
logging.Error(fmt.Errorf("%+v", err), "Unexpected panic when consuming recyclerCh")
45+
}
46+
}()
4147
for task := range recyclerCh {
4248
recycler := getRecyclerOfResource(task.resource)
4349
recycler.scheduleNodes(task.nodes)

core/outlier/recycler_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func generateNodes(n int) []string {
5959
return nodes
6060
}
6161

62-
func TestRecycler(t *testing.T) {
62+
func testRecycler(t *testing.T) {
6363
nodes := []string{"node0", "node1"}
6464
resource := "testResource"
6565
addNodeBreakers(resource, nodes)
@@ -79,7 +79,7 @@ func TestRecycler(t *testing.T) {
7979
assert.Contains(t, m, nodes[0]) // node0 should have been recovered
8080
}
8181

82-
func TestRecyclerConcurrent(t *testing.T) {
82+
func testRecyclerConcurrent(t *testing.T) {
8383
nodes := generateNodes(100) // Generate 100 nodes
8484
resource := "testResource"
8585
addNodeBreakers(resource, nodes)
@@ -119,7 +119,7 @@ func TestRecyclerConcurrent(t *testing.T) {
119119
assert.Contains(t, m, nodes[1])
120120
}
121121

122-
func TestRecyclerCh(t *testing.T) {
122+
func testRecyclerCh(t *testing.T) {
123123
nodes := []string{"node0", "node1"}
124124
resource := "testResource"
125125
addNodeBreakers(resource, nodes)
@@ -139,7 +139,7 @@ func TestRecyclerCh(t *testing.T) {
139139
assert.Contains(t, m, nodes[0]) // node0 should have been recovered
140140
}
141141

142-
func TestRecyclerChConcurrent(t *testing.T) {
142+
func testRecyclerChConcurrent(t *testing.T) {
143143
nodes := generateNodes(100) // Generate 100 nodes
144144
resource := "testResource"
145145
addNodeBreakers(resource, nodes)
@@ -179,3 +179,10 @@ func TestRecyclerChConcurrent(t *testing.T) {
179179
assert.Contains(t, m, nodes[0])
180180
assert.Contains(t, m, nodes[1])
181181
}
182+
183+
func TestRecyclerAll(t *testing.T) {
184+
t.Run("TestRecycler", testRecycler)
185+
t.Run("TestRecyclerConcurrent", testRecyclerConcurrent)
186+
t.Run("TestRecyclerCh", testRecyclerCh)
187+
t.Run("TestRecyclerChConcurrent", testRecyclerChConcurrent)
188+
}

core/outlier/retryer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package outlier
1616

1717
import (
1818
"errors"
19+
"fmt"
1920
"net"
2021
"sync"
2122
"time"
@@ -32,6 +33,11 @@ var (
3233

3334
func init() {
3435
go func() {
36+
defer func() {
37+
if err := recover(); err != nil {
38+
logging.Error(fmt.Errorf("%+v", err), "Unexpected panic when consuming retryerCh")
39+
}
40+
}()
3541
for task := range retryerCh {
3642
retryer := getRetryerOfResource(task.resource)
3743
retryer.scheduleNodes(task.nodes)

core/outlier/retryer_test.go

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,47 +28,57 @@ import (
2828
"github.com/alibaba/sentinel-golang/core/circuitbreaker"
2929
)
3030

31-
var callCounts = make(map[string]int)
32-
var recoverCount int
33-
var mu sync.Mutex
31+
type dummyCall struct {
32+
callCounts map[string]int
33+
recoverCount int
34+
mtx sync.Mutex
35+
}
36+
37+
func newDummyCall() *dummyCall {
38+
return &dummyCall{
39+
callCounts: make(map[string]int),
40+
}
41+
}
3442

35-
func registerAddress(address string, n int) {
36-
mu.Lock()
37-
defer mu.Unlock()
38-
callCounts[address] = n
43+
func (d *dummyCall) registerAddress(address string, n int) {
44+
d.mtx.Lock()
45+
defer d.mtx.Unlock()
46+
d.callCounts[address] = n
3947
}
4048

41-
// dummyCall checks whether the node address has returned to normal.
49+
// dummyCall's Check checks whether the node address has returned to normal.
4250
// It returns to normal when the value recorded in callCounts decreases to 0.
43-
func dummyCall(address string) bool {
44-
mu.Lock()
45-
defer mu.Unlock()
46-
if _, ok := callCounts[address]; ok {
47-
callCounts[address]--
51+
func (d *dummyCall) Check(address string) bool {
52+
d.mtx.Lock()
53+
defer d.mtx.Unlock()
54+
if _, ok := d.callCounts[address]; ok {
55+
d.callCounts[address]--
4856
time.Sleep(100 * time.Millisecond) // simulate network latency
49-
if callCounts[address] == 0 {
57+
if d.callCounts[address] == 0 {
5058
fmt.Printf("%s successfully reconnected\n", address)
51-
recoverCount++
59+
d.recoverCount++
5260
return true
5361
}
5462
return false
5563
}
64+
fmt.Println(d.callCounts)
5665
panic("Attempting to call an unregistered node address.")
66+
return false
5767
}
5868

59-
func getRecoverCount() int {
60-
mu.Lock()
61-
defer mu.Unlock()
62-
return recoverCount
69+
func (d *dummyCall) getRecoverCount() int {
70+
d.mtx.Lock()
71+
defer d.mtx.Unlock()
72+
return d.recoverCount
6373
}
6474

65-
func addOutlierRuleForRetryer(resource string, n, internal uint32) {
75+
func addOutlierRuleForRetryer(resource string, n, internal uint32, f RecoveryCheckFunc) {
6676
updateMux.Lock()
6777
defer updateMux.Unlock()
6878
outlierRules[resource] = &Rule{
6979
MaxRecoveryAttempts: n,
7080
RecoveryIntervalMs: internal,
71-
RecoveryCheckFunc: dummyCall,
81+
RecoveryCheckFunc: f,
7282
}
7383
}
7484

@@ -124,14 +134,15 @@ func setNodeBreaker(resource string, node string, breaker *MockCircuitBreaker) {
124134
// Construct two dummy node addresses: the first one recovers after the third check,
125135
// and the second one recovers after math.MaxInt32 checks. Observe the changes in the
126136
// circuit breaker and callCounts status for the first node before and after recovery.
127-
func TestRetryer(t *testing.T) {
128-
resource := "testResource"
137+
func testRetryer(t *testing.T) {
138+
resource := "testResource0"
129139
nodes := []string{"node0", "node1"}
130140
var internal, n uint32 = 1000, 3
131-
registerAddress(nodes[0], int(n))
132-
registerAddress(nodes[1], math.MaxInt32)
141+
d := newDummyCall()
142+
d.registerAddress(nodes[0], int(n))
143+
d.registerAddress(nodes[1], math.MaxInt32)
133144

134-
addOutlierRuleForRetryer(resource, n, internal)
145+
addOutlierRuleForRetryer(resource, n, internal, d.Check)
135146
retryer := getRetryerOfResource(resource)
136147
retryer.scheduleNodes(nodes)
137148

@@ -140,31 +151,32 @@ func TestRetryer(t *testing.T) {
140151
setNodeBreaker(resource, nodes[0], mockCB)
141152

142153
minDuration := time.Duration(n * (n + 1) / 2 * internal * 1e6)
143-
for getRecoverCount() < 1 {
154+
for d.getRecoverCount() < 1 {
144155
time.Sleep(minDuration)
145156
}
146157
assert.Equal(t, len(nodes)-1, len(retryer.counts))
147158
mockCB.AssertExpectations(t)
148159
}
149160

150-
func TestRetryerConcurrent(t *testing.T) {
151-
resource := "testResource"
161+
func testRetryerConcurrent(t *testing.T) {
162+
resource := "testResource1"
152163
nodes := generateNodes(100) // Generate 100 nodes
153164
var internal, n uint32 = 1000, 3
165+
d := newDummyCall()
154166
mockCBs := make([]*MockCircuitBreaker, 0, len(nodes)/2)
155167
for i, node := range nodes {
156168
if i%2 == 0 {
157169
mockCB := new(MockCircuitBreaker)
158170
mockCB.On("OnRequestComplete", mock.AnythingOfType("uint64"), nil).Return()
159171
setNodeBreaker(resource, node, mockCB)
160172
mockCBs = append(mockCBs, mockCB)
161-
registerAddress(node, int(n))
173+
d.registerAddress(node, int(n))
162174
} else {
163-
registerAddress(node, math.MaxInt32)
175+
d.registerAddress(node, math.MaxInt32)
164176
}
165177
}
166-
167-
addOutlierRuleForRetryer(resource, n, internal)
178+
fmt.Println(d.callCounts)
179+
addOutlierRuleForRetryer(resource, n, internal, d.Check)
168180
retryer := getRetryerOfResource(resource)
169181
numGoroutines := 10
170182
var wg sync.WaitGroup
@@ -187,7 +199,7 @@ func TestRetryerConcurrent(t *testing.T) {
187199
assert.Equal(t, len(nodes), len(retryer.counts))
188200

189201
minDuration := time.Duration(n * (n + 1) / 2 * internal * 1e6)
190-
for getRecoverCount() < len(nodes)/2 {
202+
for d.getRecoverCount() < len(nodes)/2 {
191203
time.Sleep(minDuration)
192204
}
193205
assert.Equal(t, len(nodes)/2, len(retryer.counts))
@@ -196,14 +208,15 @@ func TestRetryerConcurrent(t *testing.T) {
196208
}
197209
}
198210

199-
func TestRetryerCh(t *testing.T) {
211+
func testRetryerCh(t *testing.T) {
200212
nodes := []string{"node0", "node1"}
201-
resource := "testResource"
213+
resource := "testResource2"
202214
var internal, n uint32 = 1000, 3
203-
registerAddress(nodes[0], int(n))
204-
registerAddress(nodes[1], math.MaxInt32)
215+
d := newDummyCall()
216+
d.registerAddress(nodes[0], int(n))
217+
d.registerAddress(nodes[1], math.MaxInt32)
205218

206-
addOutlierRuleForRetryer(resource, n, internal)
219+
addOutlierRuleForRetryer(resource, n, internal, d.Check)
207220
retryer := getRetryerOfResource(resource)
208221

209222
mockCB := new(MockCircuitBreaker)
@@ -213,9 +226,15 @@ func TestRetryerCh(t *testing.T) {
213226
retryerCh <- task{nodes, resource}
214227

215228
minDuration := time.Duration(n * (n + 1) / 2 * internal * 1e6)
216-
for getRecoverCount() < 1 {
229+
for d.getRecoverCount() < 1 {
217230
time.Sleep(minDuration)
218231
}
219232
assert.Equal(t, len(nodes)-1, len(retryer.counts))
220233
mockCB.AssertExpectations(t)
221234
}
235+
236+
func TestRetryerAll(t *testing.T) {
237+
t.Run("TestRetryer", testRetryer)
238+
t.Run("TestRetryerConcurrent", testRetryerConcurrent)
239+
t.Run("TestRetryerCh", testRetryerCh)
240+
}

core/outlier/slot.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult {
4040
if len(resource) == 0 {
4141
return result
4242
}
43+
4344
filterNodes, outlierNodes, halfOpenNodes := checkAllNodes(ctx)
4445
result.SetFilterNodes(filterNodes)
4546
result.SetHalfOpenNodes(halfOpenNodes)
47+
4648
if len(outlierNodes) != 0 {
47-
if len(retryerCh) < capacity {
49+
rule := getOutlierRuleOfResource(resource)
50+
if rule.EnableActiveRecovery && len(retryerCh) < capacity {
4851
retryerCh <- task{outlierNodes, resource}
4952
}
5053
if len(recyclerCh) < capacity {
@@ -66,9 +69,7 @@ func checkAllNodes(ctx *base.EntryContext) (filters []string, outliers []string,
6669
}
6770
continue
6871
}
69-
if rule.EnableActiveRecovery {
70-
outliers = append(outliers, address)
71-
}
72+
outliers = append(outliers, address)
7273
if len(filters) < int(float64(nodeCount)*rule.MaxEjectionPercent) {
7374
filters = append(filters, address)
7475
}

0 commit comments

Comments
 (0)