github.com/searKing/golang/go@v1.2.117/time/rate/rate_test.go (about)

     1  // Copyright 2020 The searKing Author. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package rate
     6  
     7  import (
     8  	"context"
     9  	"runtime"
    10  	"runtime/debug"
    11  	"sync"
    12  	"sync/atomic"
    13  	"testing"
    14  	"time"
    15  
    16  	time_ "github.com/searKing/golang/go/time"
    17  )
    18  
    19  const (
    20  	d = 100 * time.Millisecond
    21  )
    22  
    23  type allow struct {
    24  	n  int
    25  	ok bool
    26  }
    27  
    28  func runAllow(t *testing.T, lim *BurstLimiter, allows []allow) {
    29  	for i, allow := range allows {
    30  		ok := lim.AllowN(allow.n)
    31  		if ok {
    32  			lim.PutTokenN(allow.n)
    33  		}
    34  		if ok != allow.ok {
    35  			t.Errorf("step %d: lim.AllowN(%v) = %v want %v", i, allow.n, ok, allow.ok)
    36  		}
    37  	}
    38  }
    39  
    40  func TestLimiterBurst1(t *testing.T) {
    41  	runAllow(t, NewFullBurstLimiter(1), []allow{
    42  		{1, true},
    43  		{2, false}, // burst size is 1, so n=2 always fails
    44  	})
    45  }
    46  
    47  func TestLimiterBurst3(t *testing.T) {
    48  	runAllow(t, NewFullBurstLimiter(3), []allow{
    49  		{1, true},
    50  		{2, true},
    51  		{3, true},
    52  		{4, false}, // burst size is 1, so n=3 always fails
    53  	})
    54  }
    55  
    56  func TestSimultaneousRequests(t *testing.T) {
    57  	const (
    58  		burst       = 5
    59  		numRequests = 15
    60  	)
    61  	var (
    62  		wg    sync.WaitGroup
    63  		numOK = uint32(0)
    64  	)
    65  
    66  	// Very slow replenishing bucket.
    67  	lim := NewFullBurstLimiter(burst)
    68  
    69  	// Tries to take a token, atomically updates the counter and decreases the wait
    70  	// group counter.
    71  	f := func() {
    72  		defer wg.Done()
    73  		if ok := lim.Allow(); ok {
    74  			atomic.AddUint32(&numOK, 1)
    75  		}
    76  	}
    77  
    78  	wg.Add(numRequests)
    79  	for i := 0; i < numRequests; i++ {
    80  		go f()
    81  	}
    82  	wg.Wait()
    83  	if numOK != burst {
    84  		t.Errorf("numOK = %d, want %d", numOK, burst)
    85  	}
    86  }
    87  
    88  func TestLongRunningQPS(t *testing.T) {
    89  	if testing.Short() {
    90  		t.Skip("skipping in short mode")
    91  	}
    92  	if runtime.GOOS == "openbsd" {
    93  		t.Skip("low resolution time.Sleep invalidates test (golang.org/issue/14183)")
    94  		return
    95  	}
    96  
    97  	// The test runs for a few seconds executing many requests and then checks
    98  	// that overall number of requests is reasonable.
    99  	const (
   100  		burst = 100
   101  	)
   102  	var numOK = int32(0)
   103  
   104  	lim := NewFullBurstLimiter(burst)
   105  
   106  	var wg sync.WaitGroup
   107  	f := func() {
   108  		if ok := lim.Allow(); ok {
   109  			atomic.AddInt32(&numOK, 1)
   110  		}
   111  		wg.Done()
   112  	}
   113  
   114  	start := time.Now()
   115  	end := start.Add(5 * time.Second)
   116  	for time.Now().Before(end) {
   117  		wg.Add(1)
   118  		go f()
   119  
   120  		// This will still offer ~500 requests per second, but won't consume
   121  		// outrageous amount of CPU.
   122  		time.Sleep(2 * time.Millisecond)
   123  	}
   124  	wg.Wait()
   125  	ideal := burst
   126  
   127  	// We should never get more requests than allowed.
   128  	if want := int32(ideal + 1); numOK > want {
   129  		t.Errorf("numOK = %d, want %d (ideal %d)", numOK, want, ideal)
   130  	}
   131  	// We should get very close to the number of requests allowed.
   132  	if want := int32(0.999 * float64(ideal)); numOK < want {
   133  		t.Errorf("numOK = %d, want %d (ideal %d)", numOK, want, ideal)
   134  	}
   135  }
   136  
   137  type reserve struct {
   138  	n     int
   139  	ready bool
   140  }
   141  
   142  func runReserve(t *testing.T, lim *BurstLimiter, req reserve) *Reservation {
   143  	return runReserveMax(t, lim, req, time_.InfDuration)
   144  }
   145  
   146  func runReserveMax(t *testing.T, lim *BurstLimiter, req reserve, maxReserve time.Duration) *Reservation {
   147  	ctx, cancel := context.WithTimeout(context.Background(), maxReserve)
   148  	defer cancel()
   149  	r := lim.reserveN(ctx, req.n, false, false)
   150  	if r.Ready() != req.ready {
   151  		t.Errorf("lim.reserveN(%v, %v) = (%v) want (%v)", req.n, maxReserve, r.Ready(), req.ready)
   152  	}
   153  	return r
   154  }
   155  
   156  func TestSimpleReserve(t *testing.T) {
   157  	lim := NewFullBurstLimiter(2)
   158  
   159  	runReserve(t, lim, reserve{2, true}).PutToken()
   160  	runReserve(t, lim, reserve{2, true}).PutToken()
   161  	runReserve(t, lim, reserve{2, true}).PutToken()
   162  }
   163  
   164  func TestSimpleReserveGC(t *testing.T) {
   165  	// disable GC so we can control when it happens.
   166  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   167  	var rc bool
   168  	{
   169  		lim := NewEmptyBurstLimiter(1)
   170  		r := lim.Reserve(context.Background())
   171  		r.canceled = func() {
   172  			rc = true
   173  		}
   174  		// After one GC, the BurstLimiter should keep the reservation alive.
   175  		runtime.GC()
   176  		if rc {
   177  			t.Errorf("reservation should be alive after gc")
   178  		}
   179  		runtime.KeepAlive(r)
   180  
   181  		// A second GC should drop the reservation.
   182  		runtime.GC()
   183  		if !rc {
   184  			t.Errorf("reservation should be dropped after gc")
   185  		}
   186  	}
   187  }
   188  
   189  func TestMix(t *testing.T) {
   190  	lim := NewFullBurstLimiter(2)
   191  
   192  	runReserve(t, lim, reserve{3, false}).PutToken() // should return false because n > Burst
   193  	runReserve(t, lim, reserve{2, true}).PutToken()
   194  	runAllow(t, lim, []allow{{3, false}}) // not enough tokens - don't allow
   195  	runReserve(t, lim, reserve{2, true}).PutToken()
   196  	runAllow(t, lim, []allow{{1, true}})
   197  }
   198  
   199  func TestCancelInvalid(t *testing.T) {
   200  	lim := NewFullBurstLimiter(2)
   201  
   202  	r := runReserve(t, lim, reserve{2, true})
   203  	defer r.PutToken()
   204  	runReserve(t, lim, reserve{3, false}) // should have no effect
   205  	runReserve(t, lim, reserve{2, false}) // did not get extra tokens
   206  }
   207  
   208  func TestCancelLast(t *testing.T) {
   209  	lim := NewFullBurstLimiter(2)
   210  
   211  	runReserve(t, lim, reserve{2, true}).PutToken()
   212  	r := runReserve(t, lim, reserve{2, true})
   213  	r.PutToken() // got 2 tokens back
   214  	runReserve(t, lim, reserve{2, true}).PutToken()
   215  }
   216  
   217  func TestCancelTooLate(t *testing.T) {
   218  	lim := NewFullBurstLimiter(2)
   219  
   220  	runReserve(t, lim, reserve{2, true}).PutToken()
   221  	r := runReserve(t, lim, reserve{2, true})
   222  	runReserve(t, lim, reserve{2, false})
   223  	r.PutToken() // too late to cancel - should have no effect
   224  }
   225  
   226  func TestCancel1Tokens(t *testing.T) {
   227  	lim := NewFullBurstLimiter(2)
   228  
   229  	runReserve(t, lim, reserve{2, true}).PutToken()
   230  	r := runReserve(t, lim, reserve{1, true})
   231  	runReserve(t, lim, reserve{1, true})
   232  	runReserve(t, lim, reserve{1, false})
   233  	r.PutToken() // got 1 tokens back
   234  	runReserve(t, lim, reserve{1, true})
   235  }
   236  
   237  func TestCancel2Token(t *testing.T) {
   238  	lim := NewFullBurstLimiter(2)
   239  
   240  	runReserve(t, lim, reserve{2, true}).PutToken()
   241  	r := runReserve(t, lim, reserve{2, true})
   242  	runReserve(t, lim, reserve{1, false})
   243  	r.PutToken() // got 2 token back
   244  	runReserve(t, lim, reserve{2, true})
   245  }
   246  
   247  func TestCancelMulti(t *testing.T) {
   248  	lim := NewFullBurstLimiter(4)
   249  
   250  	runReserve(t, lim, reserve{4, true}).PutToken()
   251  	rA := runReserve(t, lim, reserve{3, true})
   252  	runReserve(t, lim, reserve{1, true}).PutToken()
   253  	rC := runReserve(t, lim, reserve{1, true})
   254  	rC.PutToken() // get 1 token back
   255  	rA.PutToken() // get 3+1 tokens back, as if C was never reserved
   256  	runReserve(t, lim, reserve{4, true})
   257  }
   258  
   259  func TestReserveJumpBack(t *testing.T) {
   260  	lim := NewFullBurstLimiter(2)
   261  
   262  	runReserve(t, lim, reserve{2, true}).PutToken() // start at t1
   263  	runReserve(t, lim, reserve{1, true}).PutToken() // should violate Limit,Burst
   264  	runReserve(t, lim, reserve{2, true}).PutToken()
   265  }
   266  
   267  func TestReserveJumpBackCancel(t *testing.T) {
   268  	lim := NewFullBurstLimiter(2)
   269  
   270  	runReserve(t, lim, reserve{2, true}).PutToken() // start at t1
   271  	r := runReserve(t, lim, reserve{2, true})
   272  	runReserve(t, lim, reserve{0, true})
   273  	runReserve(t, lim, reserve{1, false})
   274  	r.PutToken()                         // cancel at get 1 token back
   275  	runReserve(t, lim, reserve{2, true}) // should violate Limit,Burst
   276  }
   277  
   278  func TestReserveMax(t *testing.T) {
   279  	lim := NewFullBurstLimiter(2)
   280  	maxT := d
   281  
   282  	runReserveMax(t, lim, reserve{2, true}, maxT).PutToken()
   283  	runReserveMax(t, lim, reserve{1, true}, maxT).PutToken() // reserve for close future
   284  }
   285  
   286  type wait struct {
   287  	name   string
   288  	ctx    context.Context
   289  	n      int
   290  	nilErr bool
   291  }
   292  
   293  func runWait(t *testing.T, lim *BurstLimiter, w wait) {
   294  	err := lim.WaitN(w.ctx, w.n)
   295  	if (w.nilErr && err != nil) || (!w.nilErr && err == nil) {
   296  		errString := "<nil>"
   297  		if !w.nilErr {
   298  			errString = "<non-nil error>"
   299  		}
   300  		t.Errorf("lim.WaitN(%v, lim, %v) = %v; want %v",
   301  			w.name, w.n, err, errString)
   302  	}
   303  }
   304  
   305  func TestWaitSimple(t *testing.T) {
   306  	lim := NewFullBurstLimiter(3)
   307  
   308  	ctx, cancel := context.WithCancel(context.Background())
   309  	cancel()
   310  	runWait(t, lim, wait{"already-cancelled", ctx, 1, false})
   311  
   312  	runWait(t, lim, wait{"exceed-burst-error", context.Background(), 4, false})
   313  
   314  	ctx, cancel = context.WithTimeout(context.Background(), time.Second)
   315  	runWait(t, lim, wait{"act-now", ctx, 2, true})
   316  	lim.PutTokenN(2)
   317  	runWait(t, lim, wait{"act-later", ctx, 3, true})
   318  }
   319  
   320  func TestWaitTimeout(t *testing.T) {
   321  	lim := NewFullBurstLimiter(3)
   322  
   323  	ctx, cancel := context.WithTimeout(context.Background(), d)
   324  	defer cancel()
   325  	runWait(t, lim, wait{"act-now", ctx, 2, true})
   326  	runWait(t, lim, wait{"w-timeout-err", ctx, 3, false})
   327  }
   328  
   329  func TestWaitBlock(t *testing.T) {
   330  	lim := NewFullBurstLimiter(1)
   331  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   332  	defer cancel()
   333  	runWait(t, lim, wait{"act-now", context.Background(), 1, true})
   334  	runtime.GC()
   335  	runWait(t, lim, wait{"w-timeout-err", ctx, 1, false})
   336  	// timeout
   337  	lim.PutToken()
   338  	{
   339  		ctx, cancel = context.WithTimeout(context.Background(), time.Second)
   340  		defer cancel()
   341  		runWait(t, lim, wait{"act-later", ctx, 1, true})
   342  		runtime.GC()
   343  		runWait(t, lim, wait{"w-timeout-err", ctx, 1, false})
   344  	}
   345  
   346  }
   347  
   348  func BenchmarkAllowN(b *testing.B) {
   349  	lim := NewFullBurstLimiter(1)
   350  	b.ReportAllocs()
   351  	b.ResetTimer()
   352  	b.RunParallel(func(pb *testing.PB) {
   353  		for pb.Next() {
   354  			lim.AllowN(1)
   355  		}
   356  	})
   357  }
   358  
   359  func BenchmarkWaitNNoDelay(b *testing.B) {
   360  	lim := NewFullBurstLimiter(b.N)
   361  	ctx := context.Background()
   362  	b.ReportAllocs()
   363  	b.ResetTimer()
   364  	for i := 0; i < b.N; i++ {
   365  		lim.WaitN(ctx, 1)
   366  	}
   367  }
   368  
   369  func TestSimultaneousLongRequests(t *testing.T) {
   370  	const (
   371  		burst       = 5
   372  		numRequests = 15
   373  	)
   374  	var (
   375  		timeout = 1 * time.Millisecond
   376  	)
   377  	var (
   378  		wg    sync.WaitGroup
   379  		numOK = uint32(0)
   380  	)
   381  
   382  	// Very slow replenishing bucket.
   383  	lim := NewFullBurstLimiter(burst)
   384  
   385  	// Tries to take a token, atomically updates the counter and decreases the wait
   386  	// group counter.
   387  	f := func(i int) {
   388  		if i < numRequests {
   389  			defer wg.Done()
   390  		}
   391  		var limiterCtx = context.Background()
   392  		var cancel context.CancelFunc
   393  		if timeout > 0 {
   394  			limiterCtx, cancel = context.WithTimeout(context.Background(), timeout)
   395  			defer cancel()
   396  		}
   397  		if err := lim.Wait(limiterCtx); err != nil {
   398  			t.Logf("#%d wait expect ready, got err %s", i, err)
   399  		} else {
   400  			atomic.AddUint32(&numOK, 1)
   401  			defer lim.PutToken()
   402  			t.Logf("#%d got token", i)
   403  			time.Sleep(time.Second)
   404  			t.Logf("#%d put token", i)
   405  		}
   406  	}
   407  
   408  	wg.Add(numRequests)
   409  	for i := 0; i < numRequests; i++ {
   410  		go f(i)
   411  	}
   412  	wg.Wait()
   413  	f(numRequests)
   414  	if numOK != burst+1 {
   415  		t.Errorf("numOK = %d, want %d", numOK, numRequests)
   416  	}
   417  }