disgo
disgo copied to clipboard
add Per Resource Rate Limits
Rate Limits have been merged in https://github.com/switchupcb/disgo/pull/14 with one caveat.
Problem
Per Resource (and Per Resource Per Route) rate limits are not yet implemented.
The main reason is due to a lack of information by Discord regarding the following statement: "During calculation, per-route rate limits often account for top-level resources within the path using an identifier". This statement is rather ambiguous because "often" implies that it does NOT apply to every request containing a top-level resource; or started by a top-level resource. The problem is that this statement is also the only sentence regarding Discord's Per Resource Rate Limits; barring X-RateLimit-Scope
which only indicates that a 429'd request is a Per Resource Rate Limit. There is also no clarification why the X-RateLimit-Scope
calls Per Resource rate limits shared
, which could imply that multiple users can trigger than rather than just the bot.
If we knew the pattern to identify top-level resources, it could be implemented in Disgo's current form. In the worst case (a misidentification of a Route rate limit as a Per Resource rate limit), excessive maps of RouteID's to Hashes are made when the Bucket remains the same; but the Bucket isn't actually duplicated. Unfortunately, that pattern is not confirmed.
Most if not all libraries don't care about Per Resource rate limits since they are not counted against you. As an example where Per Resource rate limits are handled, DiscordGo creates a custom Per Route rate limit bucket for a Per Resource (Emoji) rate limit; limiting it's efficiency. Discord itself states that 429's are inevitable for rate limits such as Emojis, but then also disapproves the strategy of hitting a 429 and adjusting from there.
Implementation
The implementation of Per Resource (and Per Resource Per Route) rate limits in Disgo is rather straightforward.
- Change Route ID's from
uint16
tostrings
(which means the rate limiter interface must use strings). - Modify the
Send
functions of endpoints in the list of known Per Resource Routes (using Copygen).
These endpoints would provide the route-number (i.e 1
), top-level resource (i.e g
,c
,w
), and top-level resource id (i.e snowflake 42123...
) to the SendRequest
function. This would result in a hash that looks something like 1g41231
which does not collide with Discord's bucket hash, while still remaining unique to a per resource route.
Solution
Identification
In order to solve this issue, we must first identify the pattern (or routes) used as Per Resource rate limits. As an example, we know that Emoji endpoints are all Per Resource Per Route rate limits based on a given channel ID. There are multiple ways we can identify these routes, with the simplest being https://github.com/discord/discord-api-docs/issues/5144. Otherwise, we may need to create the following experiments.
Experiment 1
Create a test that attempts every request up to 50 times a second until a 429 is hit (in that second), then outputs the X-RateLimit-Scope
with the given route accordingly. If a rate limit is NOT hit, there is no rate limit for that request. Technically the chance of missing rate limits that span longer than a second (i.e Global Gateway Ratelimit) is still possible. However, a shared
(Per Resource) rate limit is expected to contain rate limit headers. As a result, these could be used (instead of a specific time frame) to trigger 429's.
Experiment 2
Implement the Per Resource feature on all routes as-is and use logging and/or a detection feature to identify when a Route is switching too much/little. This fast tracks the implementation (by requiring it for this experiment), but this experiment may not be worth the cognitive effort.