Class: SimpleHttpService::RateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/simple_http_service/rate_limiter.rb

Overview

Sliding-window rate limiter, shared per key across all Client instances in the process. Thread safe.

Constant Summary collapse

DEFAULT_INTERVAL =
60

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(limit:, interval: DEFAULT_INTERVAL) ⇒ RateLimiter

Returns a new instance of RateLimiter.



46
47
48
49
50
51
52
53
54
# File 'lib/simple_http_service/rate_limiter.rb', line 46

def initialize(limit:, interval: DEFAULT_INTERVAL)
  raise 'rate limit must be a positive integer' unless limit.to_i.positive?
  raise 'rate limit interval must be positive' unless interval.to_f.positive?

  @limit = limit.to_i
  @interval = interval.to_f
  @timestamps = []
  @mutex = Mutex.new
end

Instance Attribute Details

#intervalObject (readonly)

Returns the value of attribute interval.



20
21
22
# File 'lib/simple_http_service/rate_limiter.rb', line 20

def interval
  @interval
end

#limitObject (readonly)

Returns the value of attribute limit.



20
21
22
# File 'lib/simple_http_service/rate_limiter.rb', line 20

def limit
  @limit
end

Class Method Details

.for(key, limit:, interval: DEFAULT_INTERVAL) ⇒ Object

Returns the limiter registered for key, creating it on first use.



24
25
26
27
28
# File 'lib/simple_http_service/rate_limiter.rb', line 24

def for(key, limit:, interval: DEFAULT_INTERVAL)
  registry_mutex.synchronize do
    registry[key] ||= new(limit: limit, interval: interval)
  end
end

.reset!Object

Drops every registered limiter. Mainly useful in tests.



31
32
33
# File 'lib/simple_http_service/rate_limiter.rb', line 31

def reset!
  registry_mutex.synchronize { registry.clear }
end

Instance Method Details

#acquire(wait: false) ⇒ Object

Consumes one slot. Raises RateLimitExceeded when the window is full, unless wait is true, in which case it sleeps until a slot frees up.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/simple_http_service/rate_limiter.rb', line 58

def acquire(wait: false)
  loop do
    retry_after = try_acquire
    return true unless retry_after

    unless wait
      raise RateLimitExceeded.new(
        "rate limit of #{limit} request(s) per #{interval.round} seconds exceeded, " \
        "retry in #{retry_after.ceil} second(s)",
        retry_after: retry_after
      )
    end

    sleep(retry_after)
  end
end

#remainingObject

Slots still available in the current window.



76
77
78
79
80
81
# File 'lib/simple_http_service/rate_limiter.rb', line 76

def remaining
  @mutex.synchronize do
    prune
    limit - @timestamps.size
  end
end