Class: OnyxCord::Internal::RateLimiter::Rest

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

Overview

Discord REST rate limiter keyed by route/major parameter and remapped to X-RateLimit-Bucket whenever Discord returns a concrete bucket id.

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) ⇒ Rest

Returns a new instance of Rest.



14
15
16
17
18
19
20
21
22
23
# File 'lib/onyxcord/internal/rate_limiter/rest.rb', line 14

def initialize(clock: -> { Time.now }, entry_ttl: DEFAULT_ENTRY_TTL, prune_interval: DEFAULT_PRUNE_INTERVAL)
  @route_buckets = {}
  @bucket_mutexes = {}
  @bucket_last_used = {}
  @global_mutex = 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



25
26
27
28
# File 'lib/onyxcord/internal/rate_limiter/rest.rb', line 25

def before_request(route, major_parameter)
  mutex_wait(mutex_for(route, major_parameter))
  mutex_wait(@global_mutex) if @global_mutex.locked?
end

#handle_rate_limit(route, major_parameter, response) ⇒ Object



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

def handle_rate_limit(route, major_parameter, response)
  headers = normalize_headers(response.headers)
  global = [true, 'true', 1, '1'].include?(headers[:x_ratelimit_global]) ||
           headers[:x_ratelimit_scope] == 'global'
  mutex = global ? @global_mutex : mutex_for(route, major_parameter)
  wait_seconds = retry_after(response, headers)

  sync_wait(wait_seconds, mutex) if wait_seconds.positive?
end

#prune!Object



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

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_mutexes.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



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

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?

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

#statsObject



60
61
62
63
64
65
66
# File 'lib/onyxcord/internal/rate_limiter/rest.rb', line 60

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