limiter_test.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package ratelimit
  2. import (
  3. "math"
  4. "sync"
  5. "sync/atomic"
  6. "testing"
  7. "time"
  8. )
  9. func mustLimiter(t testing.TB, burst int, every time.Duration) *Limiter {
  10. t.Helper()
  11. l, err := New(burst, every)
  12. if err != nil {
  13. t.Fatal(err)
  14. }
  15. return l
  16. }
  17. func TestLimiterBurstAndRefill(t *testing.T) {
  18. l, now := mustLimiter(t, 3, time.Second), int64(0)
  19. for i := 0; i < 3; i++ {
  20. if n, ok := l.allow(now); !ok || n != 0 {
  21. t.Fatalf("burst line %d: ok=%v denied=%d", i, ok, n)
  22. }
  23. }
  24. for i := 0; i < 5; i++ {
  25. if _, ok := l.allow(now); ok {
  26. t.Fatalf("line %d past the burst logged", i)
  27. }
  28. }
  29. now += int64(time.Second)
  30. if n, ok := l.allow(now); !ok || n != 5 {
  31. t.Fatalf("after one refill: ok=%v denied=%d, want true 5", ok, n)
  32. }
  33. if _, ok := l.allow(now); ok {
  34. t.Fatal("one refill earned more than one line")
  35. }
  36. // A long quiet spell refills to burst, not past it, and the count reported above does not carry over.
  37. now += int64(time.Hour)
  38. for i, want := range []uint64{1, 0, 0} {
  39. if n, ok := l.allow(now); !ok || n != want {
  40. t.Fatalf("refilled line %d: ok=%v denied=%d, want true %d", i, ok, n, want)
  41. }
  42. }
  43. if _, ok := l.allow(now); ok {
  44. t.Fatal("bucket refilled past burst")
  45. }
  46. }
  47. func TestLimiterBounds(t *testing.T) {
  48. for _, every := range []time.Duration{0, -time.Second} {
  49. l := mustLimiter(t, 1, every)
  50. for i := 0; i < 100; i++ {
  51. if _, ok := l.allow(0); !ok {
  52. t.Fatalf("every=%v limited line %d", every, i)
  53. }
  54. }
  55. }
  56. l := mustLimiter(t, 0, time.Second)
  57. if _, ok := l.allow(0); !ok {
  58. t.Fatal("burst 0 should allow one line")
  59. }
  60. if _, ok := l.allow(0); ok {
  61. t.Fatal("burst 0 should allow only one line")
  62. }
  63. }
  64. func TestLimiterOverflow(t *testing.T) {
  65. for _, c := range []struct {
  66. burst int
  67. every time.Duration
  68. }{
  69. {math.MaxInt, time.Second},
  70. {1, math.MaxInt64},
  71. {2, maxSpan/2 + 1},
  72. } {
  73. if _, err := New(c.burst, c.every); err == nil {
  74. t.Fatalf("burst %d every %d should not fit", c.burst, c.every)
  75. }
  76. }
  77. l := mustLimiter(t, 2, maxSpan/2)
  78. for i := 0; i < 2; i++ {
  79. if _, ok := l.allow(0); !ok {
  80. t.Fatalf("the largest span should allow its burst, denied call %d", i)
  81. }
  82. }
  83. if _, ok := l.allow(0); ok {
  84. t.Fatal("the largest span allowed past its burst")
  85. }
  86. }
  87. // Concurrent callers read the clock at slightly different times, which must not limit a Limiter that never limits.
  88. func TestLimiterUnlimitedConcurrent(t *testing.T) {
  89. l := mustLimiter(t, 1, 0)
  90. var denied atomic.Int64
  91. var wg sync.WaitGroup
  92. for i := 0; i < 16; i++ {
  93. wg.Add(1)
  94. go func() {
  95. defer wg.Done()
  96. for j := 0; j < 10000; j++ {
  97. if _, ok := l.Allow(); !ok {
  98. denied.Add(1)
  99. }
  100. }
  101. }()
  102. }
  103. wg.Wait()
  104. if got := denied.Load(); got != 0 {
  105. t.Fatalf("denied %d lines", got)
  106. }
  107. }
  108. func TestLimiterNil(t *testing.T) {
  109. var l *Limiter
  110. for i := 0; i < 10; i++ {
  111. if _, ok := l.Allow(); !ok {
  112. t.Fatal("nil limiter limited")
  113. }
  114. }
  115. }
  116. func TestLimiterConcurrent(t *testing.T) {
  117. l := mustLimiter(t, 10, time.Hour)
  118. var allowed atomic.Int64
  119. var reported atomic.Uint64
  120. var wg sync.WaitGroup
  121. for i := 0; i < 16; i++ {
  122. wg.Add(1)
  123. go func() {
  124. defer wg.Done()
  125. for j := 0; j < 1000; j++ {
  126. if n, ok := l.allow(0); ok {
  127. allowed.Add(1)
  128. reported.Add(n)
  129. }
  130. }
  131. }()
  132. }
  133. wg.Wait()
  134. if got := allowed.Load(); got != 10 {
  135. t.Fatalf("allowed %d lines, want the burst of 10", got)
  136. }
  137. // An allowed call that lands after some denials reports them, so the rest are still pending.
  138. if got := reported.Load() + l.denied.Load(); got != 16*1000-10 {
  139. t.Fatalf("denied %d, want %d", got, 16*1000-10)
  140. }
  141. }
  142. func BenchmarkAllowAllowed(b *testing.B) {
  143. l := mustLimiter(b, 1, time.Nanosecond)
  144. b.ReportAllocs()
  145. for i := 0; i < b.N; i++ {
  146. l.tat.Store(0)
  147. l.Allow()
  148. }
  149. }
  150. func BenchmarkAllowDenied(b *testing.B) {
  151. l := mustLimiter(b, 1, time.Hour)
  152. l.Allow()
  153. b.ReportAllocs()
  154. for i := 0; i < b.N; i++ {
  155. l.Allow()
  156. }
  157. }
  158. func BenchmarkAllowDeniedParallel(b *testing.B) {
  159. l := mustLimiter(b, 1, time.Hour)
  160. l.Allow()
  161. b.ReportAllocs()
  162. b.RunParallel(func(pb *testing.PB) {
  163. for pb.Next() {
  164. l.Allow()
  165. }
  166. })
  167. }
  168. // FuzzLimiter checks the bucket against its exact definition over arbitrary call timings.
  169. // A call is denied if and only if some allowed call a would leave too many calls for the time since a:
  170. // (allowed calls since a, plus this one) * every > burst * every + (now - a).
  171. // Every denied call must also be reported exactly once, by a later allowed call or still pending.
  172. func FuzzLimiter(f *testing.F) {
  173. f.Add(uint8(3), uint16(1000), []byte{0, 0, 0, 0, 16, 0, 0, 200, 1, 1, 1, 15, 17, 0})
  174. f.Add(uint8(1), uint16(1), []byte{0, 16, 15, 16, 0, 255, 0, 0})
  175. f.Fuzz(func(t *testing.T, burst uint8, everyUs uint16, steps []byte) {
  176. if len(steps) > 512 {
  177. steps = steps[:512]
  178. }
  179. b := int64(burst%16) + 1
  180. every := int64(everyUs%1000+1) * int64(time.Microsecond)
  181. l := mustLimiter(t, int(b), time.Duration(every))
  182. var now int64
  183. var allowedAt []int64
  184. var denied, reported uint64
  185. for _, s := range steps {
  186. // Each step advances by a sixteenth of every, so timings land on and around the refill.
  187. now += int64(s) * every / 16
  188. over := false
  189. for i, a := range allowedAt {
  190. if int64(len(allowedAt)-i+1)*every > b*every+now-a {
  191. over = true
  192. break
  193. }
  194. }
  195. n, ok := l.allow(now)
  196. if ok == over {
  197. t.Fatalf("at %d with %d allowed before: ok=%v, the definition says %v", now, len(allowedAt), ok, !over)
  198. }
  199. if !ok {
  200. denied++
  201. continue
  202. }
  203. reported += n
  204. allowedAt = append(allowedAt, now)
  205. }
  206. if got := reported + l.denied.Load(); got != denied {
  207. t.Fatalf("reported %d denied calls, made %d", got, denied)
  208. }
  209. })
  210. }
  211. // Concurrent callers on the real clock must account for every denied call exactly once.
  212. func TestLimiterDeniedConservedConcurrent(t *testing.T) {
  213. l := mustLimiter(t, 4, time.Microsecond)
  214. var denied, reported atomic.Uint64
  215. var wg sync.WaitGroup
  216. for i := 0; i < 16; i++ {
  217. wg.Add(1)
  218. go func() {
  219. defer wg.Done()
  220. for j := 0; j < 20000; j++ {
  221. if n, ok := l.Allow(); ok {
  222. reported.Add(n)
  223. } else {
  224. denied.Add(1)
  225. }
  226. }
  227. }()
  228. }
  229. wg.Wait()
  230. if denied.Load() == 0 {
  231. t.Fatal("nothing was denied, the test proves nothing")
  232. }
  233. if got := reported.Load() + l.denied.Load(); got != denied.Load() {
  234. t.Fatalf("reported %d denied calls, made %d", got, denied.Load())
  235. }
  236. }