Class: DiscordRDA::InvalidRequestBucket
- Inherits:
-
Object
- Object
- DiscordRDA::InvalidRequestBucket
- Defined in:
- lib/discord_rda/connection/invalid_bucket.rb
Overview
Production-ready Invalid Request Bucket - Prevents 1-hour Discord bans. Implements global request pausing when approaching invalid request limits.
Constant Summary collapse
- DEFAULT_LIMIT =
Default values per Discord’s documentation
10_000- DEFAULT_INTERVAL =
10 minutes in milliseconds
10 * 60 * 1000
- WARNING_THRESHOLD =
Warn when remaining drops below this
100- PAUSE_THRESHOLD =
Pause all requests when remaining drops below this
50
Instance Attribute Summary collapse
-
#globally_paused ⇒ Boolean
readonly
Whether globally paused due to approaching limit.
-
#interval ⇒ Integer
readonly
Time window in milliseconds.
-
#limit ⇒ Integer
readonly
Maximum invalid requests allowed.
-
#logger ⇒ Logger
readonly
Logger instance.
-
#remaining ⇒ Integer
readonly
Current remaining requests.
Instance Method Summary collapse
-
#handle_request(status) ⇒ void
Handle a completed request response.
-
#health_percentage ⇒ Float
Get percentage of remaining requests.
-
#healthy? ⇒ Boolean
Check if bucket is healthy.
-
#initialize(limit: DEFAULT_LIMIT, interval: DEFAULT_INTERVAL, logger: nil) ⇒ InvalidRequestBucket
constructor
Initialize invalid request bucket.
-
#release_pause ⇒ void
Release global pause (call after interval or manual intervention).
-
#request_allowed? ⇒ Boolean
Check if a request is allowed.
-
#reset ⇒ void
Reset the bucket (after interval has passed).
-
#status ⇒ Hash
Get current status with detailed information.
-
#wait_until_request_available ⇒ void
Wait until a request is allowed (not rate limited by invalid requests) Blocks if we’ve hit the invalid request limit or if globally paused.
Constructor Details
#initialize(limit: DEFAULT_LIMIT, interval: DEFAULT_INTERVAL, logger: nil) ⇒ InvalidRequestBucket
Initialize invalid request bucket
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 33 def initialize(limit: DEFAULT_LIMIT, interval: DEFAULT_INTERVAL, logger: nil) @limit = limit @interval = interval @remaining = limit @logger = logger @mutex = Mutex.new @frozen_at = nil @reset_timer = nil @globally_paused = false @pause_condition = Async::Condition.new end |
Instance Attribute Details
#globally_paused ⇒ Boolean (readonly)
Returns Whether globally paused due to approaching limit.
27 28 29 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 27 def globally_paused @globally_paused end |
#interval ⇒ Integer (readonly)
Returns Time window in milliseconds.
18 19 20 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 18 def interval @interval end |
#limit ⇒ Integer (readonly)
Returns Maximum invalid requests allowed.
15 16 17 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 15 def limit @limit end |
#logger ⇒ Logger (readonly)
Returns Logger instance.
24 25 26 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 24 def logger @logger end |
#remaining ⇒ Integer (readonly)
Returns Current remaining requests.
21 22 23 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 21 def remaining @remaining end |
Instance Method Details
#handle_request(status) ⇒ void
This method returns an undefined value.
Handle a completed request response
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 94 def handle_request(status) # Only count 401, 403, 429, and 502 as invalid requests return unless invalid_status?(status) @mutex.synchronize do @frozen_at ||= Time.now.to_f * 1000 @remaining -= 1 @logger&.debug('Invalid request counted', status: status, remaining: @remaining, limit: @limit) # Schedule automatic reset schedule_reset unless @reset_timer # Check thresholds if @remaining == WARNING_THRESHOLD @logger&.warn('Approaching invalid request limit!', remaining: @remaining) elsif @remaining == PAUSE_THRESHOLD @globally_paused = true @logger&.error('CRITICAL: Pausing all requests to prevent 1-hour Discord ban!', remaining: @remaining) elsif @remaining <= 0 @logger&.error('INVALID REQUEST LIMIT REACHED! All requests blocked for 10 minutes.') end end end |
#health_percentage ⇒ Float
Get percentage of remaining requests
185 186 187 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 185 def health_percentage @mutex.synchronize { (@remaining.to_f / @limit) * 100 } end |
#healthy? ⇒ Boolean
Check if bucket is healthy
179 180 181 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 179 def healthy? @mutex.synchronize { @remaining > WARNING_THRESHOLD } end |
#release_pause ⇒ void
This method returns an undefined value.
Release global pause (call after interval or manual intervention)
121 122 123 124 125 126 127 128 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 121 def release_pause @mutex.synchronize do was_paused = @globally_paused @globally_paused = false @logger&.info('Global pause released. Resuming normal request processing.') if was_paused @pause_condition.signal end end |
#request_allowed? ⇒ Boolean
Check if a request is allowed
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 80 def request_allowed? @mutex.synchronize do return false if @globally_paused return true if @remaining > 0 return true unless @frozen_at now = Time.now.to_f * 1000 now >= (@frozen_at + @interval) end end |
#reset ⇒ void
This method returns an undefined value.
Reset the bucket (after interval has passed)
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 132 def reset @mutex.synchronize do old_remaining = @remaining @remaining = @limit @frozen_at = nil @reset_timer = nil was_paused = @globally_paused @globally_paused = false if old_remaining < WARNING_THRESHOLD @logger&.info('Invalid request bucket reset', previous_remaining: old_remaining) end @pause_condition.signal if was_paused end end |
#status ⇒ Hash
Get current status with detailed information
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 151 def status @mutex.synchronize do now = Time.now.to_f * 1000 reset_in = if @frozen_at && @remaining <= 0 [(@frozen_at + @interval - now) / 1000.0, 0].max else nil end { limit: @limit, remaining: @remaining, used: @limit - @remaining, interval: @interval, interval_minutes: @interval / 60000.0, frozen_at: @frozen_at ? Time.at(@frozen_at / 1000.0) : nil, reset_in_seconds: reset_in, globally_paused: @globally_paused, request_allowed: request_allowed?, warning_threshold: WARNING_THRESHOLD, pause_threshold: PAUSE_THRESHOLD, healthy: @remaining > WARNING_THRESHOLD } end end |
#wait_until_request_available ⇒ void
This method returns an undefined value.
Wait until a request is allowed (not rate limited by invalid requests) Blocks if we’ve hit the invalid request limit or if globally paused
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/discord_rda/connection/invalid_bucket.rb', line 48 def wait_until_request_available @mutex.synchronize do # Wait if globally paused while @globally_paused @logger&.warn('Waiting: Globally paused due to invalid request limit') @mutex.unlock @pause_condition.wait @mutex.lock end if @remaining <= PAUSE_THRESHOLD && !@globally_paused @globally_paused = true @logger&.error('GLOBAL PAUSE ACTIVATED: Approaching invalid request limit!', remaining: @remaining) end if @remaining <= 0 && @frozen_at now = Time.now.to_f * 1000 future = @frozen_at + @interval wait_time = [(future - now) / 1000.0, 0].max if wait_time > 0 @logger&.error('Invalid request bucket exhausted! Waiting to prevent 1-hour ban.', wait_seconds: wait_time.round(2)) @mutex.unlock sleep(wait_time) @mutex.lock end end end end |