You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ratelimiter.go 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Copyright 2015 Matthew Holt
  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. package certmagic
  15. import (
  16. "context"
  17. "log"
  18. "runtime"
  19. "sync"
  20. "time"
  21. )
  22. // NewRateLimiter returns a rate limiter that allows up to maxEvents
  23. // in a sliding window of size window. If maxEvents and window are
  24. // both 0, or if maxEvents is non-zero and window is 0, rate limiting
  25. // is disabled. This function panics if maxEvents is less than 0 or
  26. // if maxEvents is 0 and window is non-zero, which is considered to be
  27. // an invalid configuration, as it would never allow events.
  28. func NewRateLimiter(maxEvents int, window time.Duration) *RingBufferRateLimiter {
  29. if maxEvents < 0 {
  30. panic("maxEvents cannot be less than zero")
  31. }
  32. if maxEvents == 0 && window != 0 {
  33. panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
  34. }
  35. rbrl := &RingBufferRateLimiter{
  36. window: window,
  37. ring: make([]time.Time, maxEvents),
  38. started: make(chan struct{}),
  39. stopped: make(chan struct{}),
  40. ticket: make(chan struct{}),
  41. }
  42. go rbrl.loop()
  43. <-rbrl.started // make sure loop is ready to receive before we return
  44. return rbrl
  45. }
  46. // RingBufferRateLimiter uses a ring to enforce rate limits
  47. // consisting of a maximum number of events within a single
  48. // sliding window of a given duration. An empty value is
  49. // not valid; use NewRateLimiter to get one.
  50. type RingBufferRateLimiter struct {
  51. window time.Duration
  52. ring []time.Time // maxEvents == len(ring)
  53. cursor int // always points to the oldest timestamp
  54. mu sync.Mutex // protects ring, cursor, and window
  55. started chan struct{}
  56. stopped chan struct{}
  57. ticket chan struct{}
  58. }
  59. // Stop cleans up r's scheduling goroutine.
  60. func (r *RingBufferRateLimiter) Stop() {
  61. close(r.stopped)
  62. }
  63. func (r *RingBufferRateLimiter) loop() {
  64. defer func() {
  65. if err := recover(); err != nil {
  66. buf := make([]byte, stackTraceBufferSize)
  67. buf = buf[:runtime.Stack(buf, false)]
  68. log.Printf("panic: ring buffer rate limiter: %v\n%s", err, buf)
  69. }
  70. }()
  71. for {
  72. // if we've been stopped, return
  73. select {
  74. case <-r.stopped:
  75. return
  76. default:
  77. }
  78. if len(r.ring) == 0 {
  79. if r.window == 0 {
  80. // rate limiting is disabled; always allow immediately
  81. r.permit()
  82. continue
  83. }
  84. panic("invalid configuration: maxEvents = 0 and window != 0 does not allow any events")
  85. }
  86. // wait until next slot is available or until we've been stopped
  87. r.mu.Lock()
  88. then := r.ring[r.cursor].Add(r.window)
  89. r.mu.Unlock()
  90. waitDuration := time.Until(then)
  91. waitTimer := time.NewTimer(waitDuration)
  92. select {
  93. case <-waitTimer.C:
  94. r.permit()
  95. case <-r.stopped:
  96. waitTimer.Stop()
  97. return
  98. }
  99. }
  100. }
  101. // Allow returns true if the event is allowed to
  102. // happen right now. It does not wait. If the event
  103. // is allowed, a ticket is claimed.
  104. func (r *RingBufferRateLimiter) Allow() bool {
  105. select {
  106. case <-r.ticket:
  107. return true
  108. default:
  109. return false
  110. }
  111. }
  112. // Wait blocks until the event is allowed to occur. It returns an
  113. // error if the context is cancelled.
  114. func (r *RingBufferRateLimiter) Wait(ctx context.Context) error {
  115. select {
  116. case <-ctx.Done():
  117. return context.Canceled
  118. case <-r.ticket:
  119. return nil
  120. }
  121. }
  122. // MaxEvents returns the maximum number of events that
  123. // are allowed within the sliding window.
  124. func (r *RingBufferRateLimiter) MaxEvents() int {
  125. r.mu.Lock()
  126. defer r.mu.Unlock()
  127. return len(r.ring)
  128. }
  129. // SetMaxEvents changes the maximum number of events that are
  130. // allowed in the sliding window. If the new limit is lower,
  131. // the oldest events will be forgotten. If the new limit is
  132. // higher, the window will suddenly have capacity for new
  133. // reservations. It panics if maxEvents is 0 and window size
  134. // is not zero.
  135. func (r *RingBufferRateLimiter) SetMaxEvents(maxEvents int) {
  136. newRing := make([]time.Time, maxEvents)
  137. r.mu.Lock()
  138. defer r.mu.Unlock()
  139. if r.window != 0 && maxEvents == 0 {
  140. panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
  141. }
  142. // only make the change if the new limit is different
  143. if maxEvents == len(r.ring) {
  144. return
  145. }
  146. // the new ring may be smaller; fast-forward to the
  147. // oldest timestamp that will be kept in the new
  148. // ring so the oldest ones are forgotten and the
  149. // newest ones will be remembered
  150. sizeDiff := len(r.ring) - maxEvents
  151. for i := 0; i < sizeDiff; i++ {
  152. r.advance()
  153. }
  154. if len(r.ring) > 0 {
  155. // copy timestamps into the new ring until we
  156. // have either copied all of them or have reached
  157. // the capacity of the new ring
  158. startCursor := r.cursor
  159. for i := 0; i < len(newRing); i++ {
  160. newRing[i] = r.ring[r.cursor]
  161. r.advance()
  162. if r.cursor == startCursor {
  163. // new ring is larger than old one;
  164. // "we've come full circle"
  165. break
  166. }
  167. }
  168. }
  169. r.ring = newRing
  170. r.cursor = 0
  171. }
  172. // Window returns the size of the sliding window.
  173. func (r *RingBufferRateLimiter) Window() time.Duration {
  174. r.mu.Lock()
  175. defer r.mu.Unlock()
  176. return r.window
  177. }
  178. // SetWindow changes r's sliding window duration to window.
  179. // Goroutines that are already blocked on a call to Wait()
  180. // will not be affected. It panics if window is non-zero
  181. // but the max event limit is 0.
  182. func (r *RingBufferRateLimiter) SetWindow(window time.Duration) {
  183. r.mu.Lock()
  184. defer r.mu.Unlock()
  185. if window != 0 && len(r.ring) == 0 {
  186. panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
  187. }
  188. r.window = window
  189. }
  190. // permit allows one event through the throttle. This method
  191. // blocks until a goroutine is waiting for a ticket or until
  192. // the rate limiter is stopped.
  193. func (r *RingBufferRateLimiter) permit() {
  194. for {
  195. select {
  196. case r.started <- struct{}{}:
  197. // notify parent goroutine that we've started; should
  198. // only happen once, before constructor returns
  199. continue
  200. case <-r.stopped:
  201. return
  202. case r.ticket <- struct{}{}:
  203. r.mu.Lock()
  204. defer r.mu.Unlock()
  205. if len(r.ring) > 0 {
  206. r.ring[r.cursor] = time.Now()
  207. r.advance()
  208. }
  209. return
  210. }
  211. }
  212. }
  213. // advance moves the cursor to the next position.
  214. // It is NOT safe for concurrent use, so it must
  215. // be called inside a lock on r.mu.
  216. func (r *RingBufferRateLimiter) advance() {
  217. r.cursor++
  218. if r.cursor >= len(r.ring) {
  219. r.cursor = 0
  220. }
  221. }