Class: NbApiClient::RateLimiter

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

Overview

A global (cross-process, cross-thread) distributed rate limiter backed by configuration.cache_store (the same store used for unauthorized-response tracking; defaults to Rails.cache). Implements a "sliding window counter" algorithm: each window is divided into fixed buckets, and the previous bucket's count is weighted by how much of it still overlaps the sliding window. This approximates a true sliding window without requiring sorted-set support, so it works with any ActiveSupport::Cache::Store (memory, Redis, Memcached, ...). All callers share one window (configure via NbApiClient.configure).

If no cache_store is configured, rate limiting is disabled entirely (every request is allowed through immediately).

Defined Under Namespace

Classes: RateLimitExhausted

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRateLimiter

Returns a new instance of RateLimiter.



25
26
27
# File 'lib/nb_api_client/rate_limiter.rb', line 25

def initialize
  @key = "nb_rate_limit:window"
end

Class Method Details

.with_limit(&block) ⇒ Object

Blocks until a request slot is available, then yields to the caller. Raises RateLimitExhausted if the wait exceeds configuration.rate_limit_max_wait_seconds.



21
22
23
# File 'lib/nb_api_client/rate_limiter.rb', line 21

def self.with_limit(&block)
  new.with_limit(&block)
end

Instance Method Details

#with_limitObject



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/nb_api_client/rate_limiter.rb', line 29

def with_limit
  config = NbApiClient.configuration
  waited = 0.0

  loop do
    result = try_acquire_slot
    if result > 0
      return yield
    else
      if waited >= config.rate_limit_max_wait_seconds
        raise RateLimitExhausted, "Rate limit exhausted after waiting #{waited}s"
      end

      # Sleep with jitter to spread retries and avoid thundering herd
      sleep_time = config.rate_limit_poll_interval + rand(0.0..config.rate_limit_max_jitter)
      sleep(sleep_time)
      waited += sleep_time
    end
  end
rescue RateLimitExhausted
  raise
rescue StandardError => e
  # Fail open: if the cache store is unavailable, allow the request through.
  # NbApiClient::Request's 429 handling acts as a safety net.
  config.logger&.warn("[NbApiClient::RateLimiter] cache store error, failing open: #{e.class} - #{e.message}")
  yield
end