Class: Philiprehberger::RateLimiter::FixedWindow

Inherits:
Object
  • Object
show all
Includes:
StatsTracking
Defined in:
lib/philiprehberger/rate_limiter/fixed_window.rb

Overview

Fixed-window rate limiter. Each key stores a single counter plus the timestamp when its current window opened — O(1) memory per key. The counter resets to zero once the window duration elapses.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from StatsTracking

#allow!, #block, #keys, #on_reject, #stats, #throttle

Constructor Details

#initialize(limit:, window:) ⇒ FixedWindow

Returns a new instance of FixedWindow.

Parameters:

  • limit (Integer)

    max requests allowed per window

  • window (Numeric)

    window duration in seconds



17
18
19
20
21
22
23
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 17

def initialize(limit:, window:)
  @limit = limit
  @window = window
  @store = {}
  @mutex = Mutex.new
  init_stats
end

Instance Attribute Details

#limitObject (readonly)

Returns the value of attribute limit.



13
14
15
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 13

def limit
  @limit
end

#windowObject (readonly)

Returns the value of attribute window.



13
14
15
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 13

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.

Parameters:

  • key (Symbol, String)

    the rate limit key

  • weight (Integer) (defaults to: 1)

    number of slots to consume (default 1)

Returns:

  • (Boolean)

    true if the request was allowed, false if rejected



30
31
32
# File 'lib/philiprehberger/rate_limiter/fixed_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.

Parameters:

  • keys (Array<Symbol, String>)

    the keys to check and consume

Returns:

  • (Hash{Object => Boolean})

    mapping of each key to the allow result



38
39
40
41
42
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 38

def allow_batch(keys)
  @mutex.synchronize do
    keys.to_h { |key| [key, try_acquire(key, 1)] }
  end
end

#clearvoid

This method returns an undefined value.

Clear state for all keys (resets quotas and stats for every tracked key)



79
80
81
82
83
84
85
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 79

def clear
  @mutex.synchronize do
    @store.clear
    @stats_store.clear
  end
  nil
end

#info(key) ⇒ Hash

Build a usage info hash for a key.

Parameters:

  • key (Symbol, String)

    the rate limit key

Returns:

  • (Hash)

    keys :remaining, :reset_at, :limit, :window, :used



91
92
93
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 91

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.

Parameters:

  • keys (Array<Symbol, String>)

    the keys to inspect

Returns:

  • (Hash{Object => Hash})

    mapping of each key to its info hash



99
100
101
102
103
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 99

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.

Parameters:

  • key (Symbol, String)

    the rate limit key

Returns:

  • (Boolean)

    true if capacity is available, false otherwise



48
49
50
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 48

def peek(key)
  @mutex.synchronize { count_remaining(key).positive? }
end

#refund(key, amount: 1) ⇒ nil

Return previously consumed slot(s) to a key (e.g. on downstream failure).

Parameters:

  • key (Symbol, String)

    the rate limit key

  • amount (Integer) (defaults to: 1)

    number of slots to refund (default 1)

Returns:

  • (nil)


110
111
112
113
114
115
116
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 110

def refund(key, amount: 1)
  @mutex.synchronize do
    bucket = roll(key)
    bucket[:count] = [bucket[:count] - amount, 0].max
    nil
  end
end

#remaining(key) ⇒ Integer

Return the number of remaining slots in the current window.

Parameters:

  • key (Symbol, String)

    the rate limit key

Returns:

  • (Integer)

    count of remaining capacity (0 if at or over limit)



56
57
58
# File 'lib/philiprehberger/rate_limiter/fixed_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.

Parameters:

  • key (Symbol, String)

    the rate limit key



72
73
74
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 72

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.

Parameters:

  • key (Symbol, String) (defaults to: :default)

    the rate limit key

Returns:

  • (Float)

    seconds until next allowed request (0.0 if allowed now)



138
139
140
141
142
143
144
145
146
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 138

def retry_after(key = :default)
  @mutex.synchronize do
    bucket = roll(key)
    return 0.0 if bucket[:count] < @limit

    wait = (bucket[:window_start] + @window) - now
    [wait, 0.0].max
  end
end

#used(key) ⇒ Integer

Number of currently consumed slots for a key in the active window.

Parameters:

  • key (Symbol, String)

    the rate limit key

Returns:

  • (Integer)

    count consumed in the current window



64
65
66
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 64

def used(key)
  @mutex.synchronize { roll(key)[:count] }
end

#wait_time(key = :default, weight: 1) ⇒ Float

Seconds until the next request would be allowed.

Parameters:

  • key (Symbol, String) (defaults to: :default)

    the rate limit key

  • weight (Integer) (defaults to: 1)

    slots needed

Returns:

  • (Float)

    seconds to wait (0 if allowed now)



123
124
125
126
127
128
129
130
131
# File 'lib/philiprehberger/rate_limiter/fixed_window.rb', line 123

def wait_time(key = :default, weight: 1)
  @mutex.synchronize do
    bucket = roll(key)
    return 0.0 if bucket[:count] + weight <= @limit

    wait = (bucket[:window_start] + @window) - now
    [wait, 0.0].max
  end
end