ratelimit.go 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /*
  2. Simple, thread-safe Go rate-limiter.
  3. Inspired by Antti Huima's algorithm on http://stackoverflow.com/a/668327
  4. Example:
  5. // Create a new rate-limiter, allowing up-to 10 calls
  6. // per second
  7. rl := ratelimit.New(10, time.Second)
  8. for i:=0; i<20; i++ {
  9. if rl.Limit() {
  10. fmt.Println("DOH! Over limit!")
  11. } else {
  12. fmt.Println("OK")
  13. }
  14. }
  15. */
  16. package ratelimit
  17. import (
  18. "sync/atomic"
  19. "time"
  20. )
  21. // RateLimiter instances are thread-safe.
  22. type RateLimiter struct {
  23. rate, allowance, max, unit, lastCheck uint64
  24. }
  25. // New creates a new rate limiter instance
  26. func New(rate int, per time.Duration) *RateLimiter {
  27. nano := uint64(per)
  28. if nano < 1 {
  29. nano = uint64(time.Second)
  30. }
  31. if rate < 1 {
  32. rate = 1
  33. }
  34. return &RateLimiter{
  35. rate: uint64(rate), // store the rate
  36. allowance: uint64(rate) * nano, // set our allowance to max in the beginning
  37. max: uint64(rate) * nano, // remember our maximum allowance
  38. unit: nano, // remember our unit size
  39. lastCheck: unixNano(),
  40. }
  41. }
  42. // UpdateRate allows to update the allowed rate
  43. func (rl *RateLimiter) UpdateRate(rate int) {
  44. atomic.StoreUint64(&rl.rate, uint64(rate))
  45. atomic.StoreUint64(&rl.max, uint64(rate)*rl.unit)
  46. }
  47. // Limit returns true if rate was exceeded
  48. func (rl *RateLimiter) Limit() bool {
  49. // Calculate the number of ns that have passed since our last call
  50. now := unixNano()
  51. passed := now - atomic.SwapUint64(&rl.lastCheck, now)
  52. // Add them to our allowance
  53. rate := atomic.LoadUint64(&rl.rate)
  54. current := atomic.AddUint64(&rl.allowance, passed*rate)
  55. // Ensure our allowance is not over maximum
  56. if max := atomic.LoadUint64(&rl.max); current > max {
  57. atomic.AddUint64(&rl.allowance, max-current)
  58. current = max
  59. }
  60. // If our allowance is less than one unit, rate-limit!
  61. if current < rl.unit {
  62. return true
  63. }
  64. // Not limited, subtract a unit
  65. atomic.AddUint64(&rl.allowance, -rl.unit)
  66. return false
  67. }
  68. // Undo reverts the last Limit() call, returning consumed allowance
  69. func (rl *RateLimiter) Undo() {
  70. current := atomic.AddUint64(&rl.allowance, rl.unit)
  71. // Ensure our allowance is not over maximum
  72. if max := atomic.LoadUint64(&rl.max); current > max {
  73. atomic.AddUint64(&rl.allowance, max-current)
  74. }
  75. }
  76. // now as unix nanoseconds
  77. func unixNano() uint64 {
  78. return uint64(time.Now().UnixNano())
  79. }