github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/cloud/circuitbreaker/breaker_test.go (about)

     1  // Copyright 2021 ByteDance Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package circuitbreaker
    16  
    17  import (
    18  	"math/rand"
    19  	"sync"
    20  	"sync/atomic"
    21  	"testing"
    22  	"time"
    23  )
    24  
    25  func BenchmarkBreaker(b *testing.B) {
    26  	op := Options{
    27  		ShouldTrip: ConsecutiveTripFunc(1000),
    28  	}
    29  	cb, _ := newBreaker(op)
    30  
    31  	b.ResetTimer()
    32  	for i := 0; i < b.N; i++ {
    33  		cb.IsAllowed()
    34  		cb.Succeed()
    35  	}
    36  }
    37  
    38  func BenchmarkBreakerParallel(b *testing.B) {
    39  	op := Options{
    40  		ShouldTrip: ConsecutiveTripFunc(1000),
    41  	}
    42  	cb, _ := newBreaker(op)
    43  
    44  	b.ResetTimer()
    45  	b.RunParallel(func(pb *testing.PB) {
    46  		for pb.Next() {
    47  			cb.IsAllowed()
    48  			cb.Succeed()
    49  		}
    50  	})
    51  }
    52  
    53  func BenchmarkBreakerParallel2Cores(b *testing.B) {
    54  	op := Options{
    55  		ShouldTrip: ConsecutiveTripFunc(1000),
    56  	}
    57  	cb, _ := newBreaker(op)
    58  	b.SetParallelism(2)
    59  
    60  	b.ResetTimer()
    61  	b.RunParallel(func(pb *testing.PB) {
    62  		for pb.Next() {
    63  			cb.IsAllowed()
    64  			cb.Succeed()
    65  		}
    66  	})
    67  }
    68  
    69  func TestBreakerConsecutiveTrip(t *testing.T) {
    70  	cooling := time.Millisecond * 50
    71  	retry := time.Millisecond * 20
    72  
    73  	op := Options{
    74  		CoolingTimeout: cooling,
    75  		DetectTimeout:  retry,
    76  		ShouldTrip:     ConsecutiveTripFunc(1000),
    77  	}
    78  	cb, _ := newBreaker(op)
    79  
    80  	for i := 0; i < 999; i++ {
    81  		assert(t, cb.IsAllowed())
    82  		cb.Fail()
    83  	}
    84  
    85  	assert(t, cb.IsAllowed())
    86  	cb.Fail()
    87  	assert(t, !cb.IsAllowed())
    88  
    89  	time.Sleep(cooling)
    90  	assert(t, cb.IsAllowed())
    91  	cb.Fail()
    92  	assert(t, !cb.IsAllowed())
    93  
    94  	time.Sleep(cooling)
    95  	assert(t, cb.IsAllowed())
    96  	cb.Succeed()
    97  
    98  	assert(t, !cb.IsAllowed())
    99  	assert(t, cb.State() == HalfOpen)
   100  
   101  	time.Sleep(retry)
   102  	assert(t, cb.IsAllowed())
   103  	cb.Succeed()
   104  	assert(t, cb.State() == Closed)
   105  
   106  	for i := 0; i < 100; i++ {
   107  		assert(t, cb.IsAllowed())
   108  		cb.Timeout()
   109  	}
   110  
   111  	assert(t, cb.IsAllowed())
   112  	cb.Succeed()
   113  
   114  	for i := 0; i < 1000; i++ {
   115  		assert(t, cb.IsAllowed())
   116  		cb.Timeout()
   117  	}
   118  
   119  	assert(t, !cb.IsAllowed())
   120  }
   121  
   122  func TestBreakerThresholdTrip(t *testing.T) {
   123  	cooling := time.Millisecond * 50
   124  	retry := time.Millisecond * 20
   125  
   126  	op := Options{
   127  		CoolingTimeout: cooling,
   128  		DetectTimeout:  retry,
   129  		ShouldTrip:     ThresholdTripFunc(1000),
   130  	}
   131  
   132  	cb, _ := newBreaker(op)
   133  
   134  	for i := 0; i < 999; i++ {
   135  		assert(t, cb.IsAllowed())
   136  		cb.Timeout()
   137  	}
   138  
   139  	for i := 0; i < 1000; i++ {
   140  		assert(t, cb.IsAllowed())
   141  		cb.Succeed()
   142  	}
   143  
   144  	assert(t, cb.IsAllowed())
   145  	cb.Fail()
   146  
   147  	assert(t, !cb.IsAllowed())
   148  }
   149  
   150  func TestBreakerInstanceTrip(t *testing.T) {
   151  	op := Options{
   152  		ShouldTrip: ConsecutiveTripFuncV2(0.5, 1000, 3*time.Second, 50, 500),
   153  	}
   154  
   155  	cb, _ := newBreaker(op)
   156  
   157  	for i := 0; i < 100; i++ {
   158  		assert(t, cb.IsAllowed())
   159  		cb.Timeout()
   160  	}
   161  	time.Sleep(3 * time.Second)
   162  	assert(t, cb.IsAllowed())
   163  	cb.Timeout()
   164  
   165  	for i := 0; i < 100; i++ {
   166  		assert(t, !cb.IsAllowed())
   167  	}
   168  }
   169  
   170  func TestBreakerRateTrip(t *testing.T) {
   171  	cooling := time.Millisecond * 50
   172  	retry := time.Millisecond * 20
   173  
   174  	op := Options{
   175  		CoolingTimeout: cooling,
   176  		DetectTimeout:  retry,
   177  		ShouldTrip:     RateTripFunc(.5, 1000),
   178  	}
   179  
   180  	cb, _ := newBreaker(op)
   181  
   182  	for i := 0; i < 499; i++ {
   183  		assert(t, cb.IsAllowed())
   184  		cb.Timeout()
   185  	}
   186  
   187  	for i := 0; i < 500; i++ {
   188  		assert(t, cb.IsAllowed())
   189  		cb.Succeed()
   190  	}
   191  
   192  	assert(t, cb.IsAllowed())
   193  	cb.Fail()
   194  	assert(t, !cb.IsAllowed())
   195  	assert(t, cb.metricer.ErrorRate() == .5)
   196  
   197  	time.Sleep(cooling)
   198  	assert(t, cb.IsAllowed())
   199  	cb.Succeed()
   200  
   201  	assert(t, cb.State() == HalfOpen)
   202  }
   203  
   204  func TestBreakerRateTrip2(t *testing.T) {
   205  	cooling := time.Millisecond
   206  	retry := time.Millisecond / 10
   207  
   208  	op := Options{
   209  		CoolingTimeout: cooling,
   210  		DetectTimeout:  retry,
   211  		ShouldTrip:     RateTripFunc(.5, 1000),
   212  	}
   213  
   214  	cb, _ := newBreaker(op)
   215  
   216  	for i := 0; i < 1000000; i++ {
   217  		if cb.IsAllowed() == false {
   218  			time.Sleep(retry)
   219  			continue
   220  		}
   221  
   222  		r := rand.Intn(100)
   223  		if r < 60 { // 60% fail
   224  			cb.Fail()
   225  		} else {
   226  			cb.Succeed()
   227  		}
   228  	}
   229  
   230  	if cb.metricer.Samples() > 1000 && cb.metricer.ErrorRate() >= .5 {
   231  		assert(t, cb.State() != Closed)
   232  
   233  	} else {
   234  		assert(t, cb.State() == Closed)
   235  	}
   236  }
   237  
   238  func TestBreakerReset(t *testing.T) {
   239  	cooling := time.Millisecond
   240  	retry := time.Millisecond / 10
   241  
   242  	op := Options{
   243  		CoolingTimeout: cooling,
   244  		DetectTimeout:  retry,
   245  		ShouldTrip:     RateTripFunc(.5, 1000),
   246  	}
   247  
   248  	cb, _ := newBreaker(op)
   249  
   250  	for i := 0; i < 1000; i++ {
   251  		assert(t, cb.IsAllowed())
   252  		cb.Timeout()
   253  	}
   254  
   255  	assert(t, cb.metricer.ErrorRate() == 1)
   256  	assert(t, cb.metricer.Samples() == 1000)
   257  	assert(t, cb.metricer.Timeouts() == 1000)
   258  	assert(t, !cb.IsAllowed())
   259  
   260  	cb.Reset()
   261  
   262  	assert(t, cb.metricer.ErrorRate() == 0)
   263  	assert(t, cb.metricer.Samples() == 0)
   264  	assert(t, cb.metricer.Timeouts() == 0)
   265  	assert(t, cb.IsAllowed())
   266  }
   267  
   268  func TestBreakerConcurrent(t *testing.T) {
   269  	cooling := time.Millisecond * 100
   270  	retry := time.Millisecond * 50
   271  	opt := Options{
   272  		CoolingTimeout: cooling,
   273  		DetectTimeout:  retry,
   274  		ShouldTrip:     RateTripFunc(0.5, 2),
   275  	}
   276  	var w sync.WaitGroup
   277  	for i := 0; i < 10; i++ {
   278  		w.Add(1)
   279  		go func() {
   280  			defer w.Done()
   281  			b, _ := newBreaker(opt)
   282  			// close -> open
   283  			for i := 0; i < 2; i++ {
   284  				b.Fail()
   285  			}
   286  			if b.State() != Open {
   287  				t.Errorf("want open state but got %s", b.State())
   288  			}
   289  
   290  			// CoolingTimeout
   291  			time.Sleep(cooling)
   292  			var wg sync.WaitGroup
   293  			pass := int32(0)
   294  			fail := int32(0)
   295  			for i := 0; i < 50; i++ {
   296  				wg.Add(1)
   297  				go func() {
   298  					defer wg.Done()
   299  					if b.IsAllowed() {
   300  						atomic.AddInt32(&pass, 1)
   301  					} else {
   302  						atomic.AddInt32(&fail, 1)
   303  					}
   304  				}()
   305  			}
   306  			wg.Wait()
   307  			if pass != 1 {
   308  				t.Errorf("want 1 pass but got %d pass", pass)
   309  			}
   310  			if fail != 49 {
   311  				t.Errorf("want 49 fails but got %d fails", fail)
   312  			}
   313  		}()
   314  	}
   315  	w.Wait()
   316  }