Class: Philiprehberger::RateLimiter::SlidingWindow
- Inherits:
-
Object
- Object
- Philiprehberger::RateLimiter::SlidingWindow
- Includes:
- StatsTracking
- Defined in:
- lib/philiprehberger/rate_limiter/sliding_window.rb
Instance Attribute Summary collapse
-
#limit ⇒ Object
readonly
Returns the value of attribute limit.
-
#window ⇒ Object
readonly
Returns the value of attribute window.
Instance Method Summary collapse
-
#allow?(key, weight: 1) ⇒ Boolean
Check if a request is allowed and consume slot(s) in the window.
-
#allow_batch(keys) ⇒ Hash{Object => Boolean}
Check many keys in a single mutex acquisition.
-
#clear ⇒ void
Clear state for all keys (resets quotas and stats for every tracked key).
-
#drain(key = :default) ⇒ Integer
Forcefully consume all remaining capacity for a key.
-
#info(key) ⇒ Hash
Build a usage info hash for a key.
-
#info_batch(keys) ⇒ Hash{Object => Hash}
Build info hashes for many keys in a single mutex acquisition.
-
#initialize(limit:, window:, max_keys: nil) ⇒ SlidingWindow
constructor
A new instance of SlidingWindow.
-
#peek(key) ⇒ Boolean
Check if a request would be allowed without consuming a slot.
-
#prune ⇒ Integer
Drop keys whose window is empty (all entries have expired), reclaiming memory for idle keys.
-
#refund(key, amount: 1) ⇒ nil
Return previously consumed slot(s) to a key (e.g. on downstream failure).
-
#remaining(key) ⇒ Integer
Return the number of remaining slots in the current window.
-
#reset(key) ⇒ void
Clear the window for a key, discarding all tracked entries.
-
#retry_after(key = :default) ⇒ Float
Seconds until the next request would be allowed, suitable for the HTTP Retry-After header.
-
#used(key) ⇒ Integer
Number of currently consumed slots for a key (after expiring old entries).
-
#wait_time(key = :default) ⇒ Float
Seconds until the next request would be allowed.
-
#window_reset_at(key = :default) ⇒ Time?
Time when the current window expires.
Methods included from StatsTracking
#allow!, #block, #keys, #on_reject, #stats, #throttle
Constructor Details
#initialize(limit:, window:, max_keys: nil) ⇒ SlidingWindow
Returns a new instance of SlidingWindow.
16 17 18 19 20 21 22 23 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 16 def initialize(limit:, window:, max_keys: nil) @limit = limit @window = window @max_keys = max_keys @store = {} @mutex = Mutex.new init_stats end |
Instance Attribute Details
#limit ⇒ Object (readonly)
Returns the value of attribute limit.
10 11 12 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 10 def limit @limit end |
#window ⇒ Object (readonly)
Returns the value of attribute window.
10 11 12 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 10 def window @window end |
Instance Method Details
#allow?(key, weight: 1) ⇒ Boolean
Check if a request is allowed and consume slot(s) in the window.
30 31 32 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 30 def allow?(key, weight: 1) @mutex.synchronize { try_acquire(key, weight) } end |
#allow_batch(keys) ⇒ Hash{Object => Boolean}
Check many keys in a single mutex acquisition.
38 39 40 41 42 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 38 def allow_batch(keys) @mutex.synchronize do keys.to_h { |key| [key, try_acquire(key, 1)] } end end |
#clear ⇒ void
This method returns an undefined value.
Clear state for all keys (resets quotas and stats for every tracked key)
82 83 84 85 86 87 88 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 82 def clear @mutex.synchronize do @store.clear @stats_store.clear end nil end |
#drain(key = :default) ⇒ Integer
Forcefully consume all remaining capacity for a key.
143 144 145 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 143 def drain(key = :default) @mutex.synchronize { drain_entries(key) } end |
#info(key) ⇒ Hash
Build a usage info hash for a key.
94 95 96 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 94 def info(key) @mutex.synchronize { build_info(key) } end |
#info_batch(keys) ⇒ Hash{Object => Hash}
Build info hashes for many keys in a single mutex acquisition.
Mirrors #allow_batch for inspection rather than acquisition.
104 105 106 107 108 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 104 def info_batch(keys) @mutex.synchronize do keys.to_h { |key| [key, build_info(key)] } end end |
#peek(key) ⇒ Boolean
Check if a request would be allowed without consuming a slot.
48 49 50 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 48 def peek(key) @mutex.synchronize { count_remaining(key).positive? } end |
#prune ⇒ Integer
Drop keys whose window is empty (all entries have expired), reclaiming memory for idle keys. Also clears their stats.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 123 def prune @mutex.synchronize do pruned = 0 tracked = @store.keys tracked.each do |key| cleanup(key) next unless @store[key].empty? @store.delete(key) @stats_store.delete(key) pruned += 1 end pruned end end |
#refund(key, amount: 1) ⇒ nil
Return previously consumed slot(s) to a key (e.g. on downstream failure).
115 116 117 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 115 def refund(key, amount: 1) @mutex.synchronize { refund_entries(key, amount) } end |
#remaining(key) ⇒ Integer
Return the number of remaining slots in the current window.
56 57 58 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 56 def remaining(key) @mutex.synchronize { count_remaining(key) } end |
#reset(key) ⇒ void
This method returns an undefined value.
Clear the window for a key, discarding all tracked entries.
75 76 77 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 75 def reset(key) @mutex.synchronize { @store.delete(key.to_s) } end |
#retry_after(key = :default) ⇒ Float
Seconds until the next request would be allowed, suitable for the HTTP Retry-After header. Returns 0.0 when a request is allowed right now.
170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 170 def retry_after(key = :default) @mutex.synchronize do cleanup(key) entries = fetch_entries(key) return 0.0 if entries.length < @limit oldest = entries.min return 0.0 if oldest.nil? wait = (oldest + @window) - now [wait, 0.0].max end end |
#used(key) ⇒ Integer
Number of currently consumed slots for a key (after expiring old entries).
64 65 66 67 68 69 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 64 def used(key) @mutex.synchronize do cleanup(key) fetch_entries(key).length end end |
#wait_time(key = :default) ⇒ Float
Seconds until the next request would be allowed
151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 151 def wait_time(key = :default) @mutex.synchronize do cleanup(key) entries = fetch_entries(key) return 0.0 if entries.length < @limit oldest = entries.min return 0.0 if oldest.nil? wait = oldest + @window - now [wait, 0.0].max end end |
#window_reset_at(key = :default) ⇒ Time?
Time when the current window expires
188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/philiprehberger/rate_limiter/sliding_window.rb', line 188 def window_reset_at(key = :default) @mutex.synchronize do entries = fetch_entries(key) return nil if entries.empty? cleanup(key) entries = fetch_entries(key) return nil if entries.empty? oldest = entries.min elapsed = now - oldest Time.now + (@window - elapsed) end end |