Class: Wikiwiki::RateLimiter

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

Overview

Rate limiter with pluggable strategies

Examples:

Raise on limit

limiter = Wikiwiki::RateLimiter.raise_on_limit([
  { window: 60, max_requests: 120 },     # 120 requests per minute
  { window: 3600, max_requests: 2000 }   # 2000 requests per hour
])
limiter.acquire!  # raises RateLimitError if limit exceeded

Wait on limit

limiter = Wikiwiki::RateLimiter.wait_on_limit([
  { window: 60, max_requests: 120 }
])
limiter.acquire!  # waits automatically if limit exceeded

No limit

limiter = Wikiwiki::RateLimiter.no_limit
limiter.acquire!  # always succeeds immediately

Constant Summary collapse

WIKIWIKI_API_LIMITS =

Default rate limits for Wikiwiki REST API

[
  {window: 60, max_requests: 120},     # 120 requests per minute
  {window: 3600, max_requests: 2000}   # 2000 requests per hour
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(limits, strategy) ⇒ RateLimiter

Returns a new instance of RateLimiter.

Parameters:

  • limits (Array<Hash>)

    array of limit configurations

  • strategy (Strategy)

    rate limiting strategy



72
73
74
75
76
# File 'lib/wikiwiki/rate_limiter.rb', line 72

def initialize(limits, strategy)
  @windows = limits.map {|limit| SlidingWindow.new(**limit) }
  @mutex = Mutex.new
  @strategy = strategy
end

Instance Attribute Details

#mutexObject (readonly)

Returns the value of attribute mutex.



113
114
115
# File 'lib/wikiwiki/rate_limiter.rb', line 113

def mutex
  @mutex
end

Class Method Details

.defaultRateLimiter

Create a rate limiter with default limits (120 requests/min, 2000 requests/hour)

These are the rate limits for the Wikiwiki REST API.

Returns:



66
# File 'lib/wikiwiki/rate_limiter.rb', line 66

def self.default = wait_on_limit(WIKIWIKI_API_LIMITS)

.no_limitRateLimiter

Create a null rate limiter that never limits

Returns:



59
# File 'lib/wikiwiki/rate_limiter.rb', line 59

def self.no_limit = new([], Strategy::Null.new)

.raise_on_limit(limits) ⇒ RateLimiter

Create a rate limiter that raises error when limit is exceeded

Parameters:

  • limits (Array<Hash>)

    array of limit configurations

Options Hash (limits):

  • :window (Integer)

    time window in seconds

  • :max_requests (Integer)

    maximum requests allowed in the window

Returns:

Raises:

  • (ArgumentError)

    if limits is empty



37
38
39
40
41
# File 'lib/wikiwiki/rate_limiter.rb', line 37

def self.raise_on_limit(limits)
  raise ArgumentError, "limits cannot be empty (use .no_limit for no rate limiting)" if limits.empty?

  new(limits, Strategy::Raise.new)
end

.wait_on_limit(limits) ⇒ RateLimiter

Create a rate limiter that waits when limit is exceeded

Parameters:

  • limits (Array<Hash>)

    array of limit configurations

Options Hash (limits):

  • :window (Integer)

    time window in seconds

  • :max_requests (Integer)

    maximum requests allowed in the window

Returns:

Raises:

  • (ArgumentError)

    if limits is empty



50
51
52
53
54
# File 'lib/wikiwiki/rate_limiter.rb', line 50

def self.wait_on_limit(limits)
  raise ArgumentError, "limits cannot be empty (use .no_limit for no rate limiting)" if limits.empty?

  new(limits, Strategy::Wait.new)
end

Instance Method Details

#acquire!void

This method returns an undefined value.

Acquire permission to make a request

Behavior depends on the strategy:

  • raise_on_limit: raises RateLimitError if limit exceeded

  • wait_on_limit: waits until request can be made

  • no_limit: always succeeds immediately

Raises:

  • (RateLimitError)

    if rate limit is exceeded (with raise strategy)



87
# File 'lib/wikiwiki/rate_limiter.rb', line 87

def acquire! = @strategy.acquire!(self)

#can_request?Boolean

Check if a request can be made without exceeding any limits

Returns:

  • (Boolean)

    true if request is allowed



101
# File 'lib/wikiwiki/rate_limiter.rb', line 101

def can_request? = @windows.all?(&:can_request?)

#record!void

This method returns an undefined value.

Record a new request across all window limiters



106
# File 'lib/wikiwiki/rate_limiter.rb', line 106

def record! = @windows.each(&:record!)

#wait_timeFloat?

Get maximum wait time across all window limiters

Returns:

  • (Float, nil)

    seconds to wait



111
# File 'lib/wikiwiki/rate_limiter.rb', line 111

def wait_time = @windows.map(&:wait_time).max

#wait_time_until_availableFloat

Get time in seconds until next request can be made

Returns:

  • (Float)

    seconds to wait (0.0 if request can be made now)



92
93
94
95
96
# File 'lib/wikiwiki/rate_limiter.rb', line 92

def wait_time_until_available
  @mutex.synchronize do
    @windows.map(&:wait_time).max || 0.0
  end
end