Skip to content

Commit

Permalink
Merge pull request #60 from Cogmasters/ratelimiting_refactor
Browse files Browse the repository at this point in the history
Ratelimiting refactor
  • Loading branch information
lcsmuller authored Apr 23, 2022
2 parents ddd3e1e + 8dc9460 commit bb0a121
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 363 deletions.
13 changes: 8 additions & 5 deletions cog-utils/chash.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@
do { \
CHASH_COUNTER_TYPE __CHASH_INDEX = 0; \
namespace ## _BUCKET *__CHASH_BUCKETS = NULL; \
int __CHASH_NEXT_SIZE = CHASH_RESIZE((hashtable)->capacity); \
CHASH_COUNTER_TYPE __CHASH_NEXT_SIZE = (CHASH_COUNTER_TYPE) \
CHASH_RESIZE((hashtable)->capacity); \
\
if((namespace ## _HEAP) == 0) { \
if((hashtable)->length != (hashtable)->capacity) { \
Expand All @@ -127,10 +128,12 @@ do { \
(double) (hashtable)->capacity < CHASH_LOAD_THRESHOLD) \
break; \
\
__CHASH_BUCKETS = malloc(__CHASH_NEXT_SIZE \
* sizeof(namespace ## _BUCKET)); \
memset(__CHASH_BUCKETS, 0, __CHASH_NEXT_SIZE \
* sizeof(namespace ## _BUCKET)); \
__CHASH_BUCKETS = malloc((size_t) (__CHASH_NEXT_SIZE \
* ((CHASH_COUNTER_TYPE) \
sizeof(namespace ## _BUCKET)))); \
memset(__CHASH_BUCKETS, 0, ((size_t) (__CHASH_NEXT_SIZE \
* ((CHASH_COUNTER_TYPE) \
sizeof(namespace ## _BUCKET))))); \
\
for(__CHASH_INDEX = 0; __CHASH_INDEX < (hashtable)->capacity; \
__CHASH_INDEX++) { \
Expand Down
3 changes: 2 additions & 1 deletion cog-utils/logconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,9 @@ logconf_branch(struct logconf *branch, struct logconf *orig, const char id[])
"Out of bounds write attempt");
}
branch->pid = getpid();

#if 0
module_is_disabled(branch);
#endif
}

void
Expand Down
199 changes: 109 additions & 90 deletions include/discord-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ struct discord_request {

/** URL endpoint threshold length */
#define DISCORD_ENDPT_LEN 512
/** Bucket's route threshold length */
/** Route's unique key threshold length */
#define DISCORD_ROUTE_LEN 256

/**
Expand All @@ -136,7 +136,7 @@ struct discord_context {
struct discord_request req;

/** the request's bucket */
struct discord_bucket *bucket;
struct discord_bucket *b;

/** request body handle @note buffer is kept and recycled */
struct {
Expand All @@ -150,8 +150,8 @@ struct discord_context {
enum http_method method;
/** the request's endpoint */
char endpoint[DISCORD_ENDPT_LEN];
/** the request's route */
char route[DISCORD_ROUTE_LEN];
/** the request bucket's key */
char key[DISCORD_ROUTE_LEN];
/** the connection handler assigned */
struct ua_conn *conn;
/** the request bucket's queue entry */
Expand All @@ -169,25 +169,11 @@ struct discord_adapter {
struct user_agent *ua;
/** curl_multi handle for performing non-blocking requests */
CURLM *mhandle;
/**
* client-side data reference counter for cleanup
* @todo replace with priority_queue.h
*/
/** client-side data reference counter for cleanup */
struct discord_refcount *refcounts;
/** routes discovered (declared at discord-adapter_ratelimit.c) */
struct _discord_routes_ht *routes;
/** buckets discovered (declared at discord-adapter_ratelimit.c) */
struct _discord_buckets_ht *buckets;

/* client-wide ratelimiting timeout */
struct {
/** global ratelimit */
u64unix_ms wait_ms;
/** global rwlock */
pthread_rwlock_t rwlock;
/** global lock */
pthread_mutex_t lock;
} * global;
/** buckets discovered (declared at discord-adapter_ratelimit.c) */
struct discord_ratelimiter *ratelimiter;

/** idle request handles */
QUEUE(struct discord_context) * idleq;
Expand All @@ -199,8 +185,8 @@ struct discord_adapter {
/**
* @brief Initialize the fields of a Discord Adapter handle
*
* @param adapter a pointer to the http handle
* @param conf optional pointer to a pre-initialized logconf
* @param adapter the adapter handle to be initialized
* @param conf optional pointer to a parent logconf
* @param token the bot token
*/
void discord_adapter_init(struct discord_adapter *adapter,
Expand Down Expand Up @@ -245,32 +231,23 @@ CCORDcode discord_adapter_run(struct discord_adapter *adapter,
CCORDcode discord_adapter_perform(struct discord_adapter *adapter);

/**
* @brief Get global timeout timestamp
*
* @param adapter the handle initialized with discord_adapter_init()
* @return the most recent global timeout timestamp
*/
u64unix_ms discord_adapter_get_global_wait(struct discord_adapter *adapter);

/**
* @brief Stop all on-going, pending and timed-out requests
* @brief Stop all bucket's on-going, pending and timed-out requests
*
* The requests will be moved over to client's 'idleq' queue
* @param adapter the handle initialized with discord_adapter_init()
*/
void discord_adapter_stop_all(struct discord_adapter *adapter);
void discord_adapter_stop_buckets(struct discord_adapter *adapter);

/**
* @brief Naive garbage collector to cleanup user arbitrary data
* @todo replace with priority_queue.h
*/
struct discord_refcount {
/** user arbitrary data to be retrieved at `done` or `fail` callbacks */
void *data;
/**
* cleanup for when `data` is no longer needed
* @note this only has to be assigned once, it shall be called once `data`
* is no longer referenced by any callback */
* @note this only has to be assigned once, it is automatically called once
* `data` is no longer referenced by any callback */
void (*cleanup)(void *data);
/** `data` references count */
int visits;
Expand Down Expand Up @@ -305,10 +282,10 @@ void discord_refcount_decr(struct discord_adapter *adapter, void *data);
* @brief Enforce ratelimiting per the official Discord Documentation
* @{ */

/** @brief The bucket struct for handling ratelimiting */
/** @brief The Discord bucket for handling per-group ratelimits */
struct discord_bucket {
/** the hash associated with this bucket */
char key[64];
/** the hash associated with the bucket's ratelimiting group */
char hash[64];
/** maximum connections this bucket can handle before ratelimit */
long limit;
/** connections this bucket can do before waiting for cooldown */
Expand All @@ -321,93 +298,135 @@ struct discord_bucket {
QUEUE(struct discord_context) waitq;
/** busy requests */
QUEUE(struct discord_context) busyq;

int state;
void *value;
};

/**
* @brief Initialize buckets and routes respective hashtables
* @brief Return bucket timeout timestamp
*
* Hashtables shall be used for storage and retrieval of discovered routes and
* buckets
* @param adapter the handle initialized with discord_adapter_init()
* @param rl the handle initialized with discord_ratelimiter_init()
* @param bucket the bucket to be checked for time out
* @return the timeout timestamp
*/
void discord_buckets_init(struct discord_adapter *adapter);
u64unix_ms discord_bucket_get_timeout(struct discord_ratelimiter *rl,
struct discord_bucket *bucket);

/**
* @brief Cleanup all buckets and routes that have been discovered
* @brief Sleep for bucket's cooldown time
* @note this **WILL** block the bucket's execution thread
*
* @param adapter the handle initialized with discord_adapter_init()
* @param rl the handle initialized with discord_ratelimiter_init()
* @param bucket the bucket to wait on cooldown
*/
void discord_buckets_cleanup(struct discord_adapter *adapter);
void discord_bucket_try_sleep(struct discord_ratelimiter *rl,
struct discord_bucket *bucket);

/**
* @brief Iterate and call `iter` callback for each discovered bucket
* @brief Get a `struct discord_bucket` assigned to `key`
*
* @param adapter the handle initialized with discord_adapter_init()
* @param iter the user callback to be called per bucket
* @param rl the handle initialized with discord_ratelimiter_init()
* @param key obtained from discord_ratelimiter_get_key()
* @return bucket matched to `key`
*/
void discord_buckets_foreach(struct discord_adapter *adapter,
void (*iter)(struct discord_adapter *adapter,
struct discord_bucket *b));
struct discord_bucket *discord_bucket_get(struct discord_ratelimiter *rl,
const char key[DISCORD_ROUTE_LEN]);

/** @brief The ratelimiter struct for handling ratelimiting */
struct discord_ratelimiter {
/** DISCORD_RATELIMIT logging module */
struct logconf conf;
/** amount of bucket's routes discovered */
int length;
/** route's cap before increase */
int capacity;
/**
* routes matched to individual buckets
* @note the `buckets` symbol here is for "hashtable buckets", and not
* Discord buckets
* @note datatype declared at discord-adapter_ratelimit.c
*/
struct _discord_route *buckets;
/** singleton bucket for requests that haven't been matched to a
* known or new bucket (i.e first time running the request) */
struct discord_bucket *null;
/** singleton bucket for requests that are not part of any known
* ratelimiting group */
struct discord_bucket *miss;

/* client-wide ratelimiting timeout */
struct {
/** global ratelimit */
u64unix_ms wait_ms;
/** global rwlock */
pthread_rwlock_t rwlock;
/** global lock */
pthread_mutex_t lock;
} global;
};

/**
* @brief Return bucket timeout timestamp
* @brief Initialize ratelimiter handle
*
* @param adapter the handle initialized with discord_adapter_init()
* @param b the bucket to be checked for time out
* @return the timeout timestamp
* A hashtable shall be used for storage and retrieval of discovered buckets
* @param conf optional pointer to a parent logconf
* @return the ratelimiter handle
*/
struct discord_ratelimiter *discord_ratelimiter_init(struct logconf *conf);

/**
* @brief Cleanup all buckets that have been discovered
*
* @note pending requests will be moved to `adapter.idleq`
* @param rl the handle initialized with discord_ratelimiter_init()
*/
u64unix_ms discord_bucket_get_timeout(struct discord_adapter *adapter,
struct discord_bucket *b);
void discord_ratelimiter_cleanup(struct discord_ratelimiter *rl);

/**
* @brief Get bucket pending cooldown time in milliseconds
* @brief Iterate known buckets
*
* @param rl the handle initialized with discord_ratelimiter_init()
* @param adapter the handle initialized with discord_adapter_init()
* @param the bucket to wait on cooldown
* @return amount to sleep for in milliseconds
* @param iter the user callback to be called per bucket
*/
int64_t discord_bucket_get_wait(struct discord_adapter *adapter,
struct discord_bucket *bucket);
void discord_ratelimiter_foreach(struct discord_ratelimiter *rl,
struct discord_adapter *adapter,
void (*iter)(struct discord_adapter *adapter,
struct discord_bucket *b));

/**
* @brief Get `route` from HTTP method and endpoint
* @brief Build unique key formed from the HTTP method and endpoint
* @see https://discord.com/developers/docs/topics/rate-limits
*
* @param method the request method
* @param route buffer filled with generated route
* @param endpoint_fmt the printf-like endpoint formatting string
* @param args variadic arguments matched to `endpoint_fmt`
* @param[in] method the request method
* @param[out] key unique key for matching to buckets
* @param[in] endpoint_fmt the printf-like endpoint formatting string
* @param[in] args variadic arguments matched to `endpoint_fmt`
*/
void discord_bucket_get_route(enum http_method method,
char route[DISCORD_ROUTE_LEN],
const char endpoint_fmt[],
va_list args);
void discord_ratelimiter_build_key(enum http_method method,
char key[DISCORD_ROUTE_LEN],
const char endpoint_fmt[],
va_list args);

/**
* @brief Get a `struct discord_bucket` assigned to `route`
* @brief Get global timeout timestamp
*
* @param adapter the handle initialized with discord_adapter_init()
* @param route route obtained from discord_bucket_get_route()
* @return bucket assigned to `route` or `adapter->b_null` if no match found
* @param rl the handle initialized with discord_ratelimiter_init()
* @return the most recent global timeout timestamp
*/
struct discord_bucket *discord_bucket_get(struct discord_adapter *adapter,
const char route[DISCORD_ROUTE_LEN]);
u64unix_ms discord_ratelimiter_get_global_wait(struct discord_ratelimiter *rl);

/**
* @brief Update the bucket with response header data
*
* @param adapter the handle initialized with discord_adapter_init()
* @param rl the handle initialized with discord_ratelimiter_init()
* @param bucket NULL when bucket is first discovered
* @param route route obtained from discord_bucket_get_route()
* @param key obtained from discord_ratelimiter_get_key()
* @param info informational struct containing details on the current transfer
* @note If the bucket was just discovered it will be created here.
*/
void discord_bucket_build(struct discord_adapter *adapter,
struct discord_bucket *bucket,
const char route[DISCORD_ROUTE_LEN],
struct ua_info *info);
void discord_ratelimiter_build(struct discord_ratelimiter *rl,
struct discord_bucket *bucket,
const char key[DISCORD_ROUTE_LEN],
struct ua_info *info);

/** @} DiscordInternalAdapterRatelimit */

Expand Down Expand Up @@ -631,14 +650,14 @@ struct discord_event {
/** the event unique id value */
enum discord_gateway_events event;
/** the event callback */
void (*on_event)(struct discord_gateway * gw);
void (*on_event)(struct discord_gateway *gw);
};

/**
* @brief Initialize the fields of Discord Gateway handle
*
* @param gw the gateway handle to be initialized
* @param conf optional pointer to a initialized logconf
* @param conf optional pointer to a parent logconf
* @param token the bot token
*/
void discord_gateway_init(struct discord_gateway *gw,
Expand Down
Loading

0 comments on commit bb0a121

Please sign in to comment.