Class: OnyxCord::Internal::RateLimiter::AsyncRest

Inherits:
Object
  • Object
show all
Defined in:
lib/onyxcord/internal/rate_limiter/async_rest.rb

Constant Summary collapse

DEFAULT_ENTRY_TTL =
3600
DEFAULT_PRUNE_INTERVAL =
100

Instance Method Summary collapse

Constructor Details

#initialize(clock: -> { Time.now }, entry_ttl: DEFAULT_ENTRY_TTL, prune_interval: DEFAULT_PRUNE_INTERVAL) ⇒ AsyncRest

Returns a new instance of AsyncRest.



12
13
14
15
16
17
18
19
20
21
# File 'lib/onyxcord/internal/rate_limiter/async_rest.rb', line 12

def initialize(clock: -> { Time.now }, entry_ttl: DEFAULT_ENTRY_TTL, prune_interval: DEFAULT_PRUNE_INTERVAL)
  @route_buckets = {}
  @bucket_locks = {}
  @bucket_last_used = {}
  @global_lock = Mutex.new
  @clock = clock
  @entry_ttl = entry_ttl
  @prune_interval = prune_interval
  @requests_since_prune = 0
end

Instance Method Details

#before_request(route, major_parameter) ⇒ Object



23
24
25
26
27
# File 'lib/onyxcord/internal/rate_limiter/async_rest.rb', line 23

def before_request(route, major_parameter)
  # Acquire locks in consistent order (route → global) to prevent deadlock.
  wait_for(mutex_for(route, major_parameter))
  wait_for(@global_lock) if @global_lock.locked?
end

#handle_rate_limit(route, major_parameter, response) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/onyxcord/internal/rate_limiter/async_rest.rb', line 49

def handle_rate_limit(route, major_parameter, response)
  headers = normalize_headers(response.headers)
  wait_seconds = retry_after(response, headers)

  return unless wait_seconds.positive?

  if headers[:x_ratelimit_global] == 'true' || headers[:x_ratelimit_scope] == 'global'
    global_wait(wait_seconds)
  else
    async_wait(wait_seconds, mutex_for(route, major_parameter))
  end
end

#prune!Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/onyxcord/internal/rate_limiter/async_rest.rb', line 70

def prune!
  return 0 unless @entry_ttl

  cutoff = @clock.call - @entry_ttl
  stale_keys = @bucket_last_used.select { |_, last_used| last_used < cutoff }.keys

  stale_keys.each do |key|
    @bucket_locks.delete(key)
    @bucket_last_used.delete(key)
    @route_buckets.delete(key)
    @route_buckets.delete_if { |_, bucket_key| bucket_key == key }
  end

  @requests_since_prune = 0
  stale_keys.length
end

#record_response(route, major_parameter, headers) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/onyxcord/internal/rate_limiter/async_rest.rb', line 29

def record_response(route, major_parameter, headers)
  headers = normalize_headers(headers)
  bucket = headers[:x_ratelimit_bucket]
  key = route_key(route, major_parameter)
  touch(key)

  if bucket
    bucket = bucket_key(bucket, major_parameter)
    @route_buckets[key] = bucket
    touch(bucket)
  end

  return unless headers[:x_ratelimit_remaining] == '0'

  wait_seconds = headers[:x_ratelimit_reset_after].to_f
  return unless wait_seconds.positive?

  async_wait(wait_seconds, mutex_for(route, major_parameter))
end

#statsObject



62
63
64
65
66
67
68
# File 'lib/onyxcord/internal/rate_limiter/async_rest.rb', line 62

def stats
  {
    route_buckets: @route_buckets.size,
    bucket_locks: @bucket_locks.size,
    tracked_keys: @bucket_last_used.size
  }
end