Greatly improve the rate limit algorithm, so it is correct for a low

ticket to period ratio.
This commit is contained in:
Nils O. Selåsdal
2013-10-15 15:03:27 +02:00
parent 41bb24bb61
commit 763032fb04
2 changed files with 19 additions and 27 deletions
+7 -11
View File
@@ -34,12 +34,7 @@
* *
* uc_ratelimit_init(&r,20, 1, time(NULL)); * uc_ratelimit_init(&r,20, 1, time(NULL));
* *
* NOTE. The the internals uses integer arithmetic to replenish the tickets, * NOTE. period * tickets * 2 must not exceed the value of a long
* this can lead to greater errors estimating the available tickets the smaller
* the number of tickets is in relation to the period. The arithmetic is scaled
* by 1000 currently, so if ticket = 1 and period = 60, we get 1000/60 = 16. This
* should be 16.6667, which is an error of about 3.6%, which means we really just
* rate limit to 1 per 62 seconds.
* *
* for (;;) { * for (;;) {
* int fd = accept(..); * int fd = accept(..);
@@ -59,12 +54,11 @@ struct RateLimit {
//internal fields: //internal fields:
//scaled up number of tickets, to avoid floating point //how much each ticket is worth.
//math long ticket_cost;
long scaled_tickets;
//number of tickets we have (scaled up) //money we have to "buy" tickets
long available_tickets; long funds;
//time of the previos period //time of the previos period
long last_ts; long last_ts;
}; };
@@ -73,6 +67,8 @@ struct RateLimit {
* Initialize a struct RateLimit, which can hand out @tickets per @period * Initialize a struct RateLimit, which can hand out @tickets per @period
* the perioid must be in the same units as the current_ts * the perioid must be in the same units as the current_ts
* *
* NOTE. period * tickets * 2 must not exceed the range of the RateLimit.funds
*
* @param r RateLimit to initialize * @param r RateLimit to initialize
* @param tickets number of tickets (bucket depth) * @param tickets number of tickets (bucket depth)
* @param period per this period * @param period per this period
+12 -16
View File
@@ -1,20 +1,17 @@
#include "ucore/rate_limit.h" #include "ucore/rate_limit.h"
#define SCALE_FACTOR 1000
void uc_ratelimit_init(struct RateLimit *r, long tickets, long period, long current_ts) void uc_ratelimit_init(struct RateLimit *r, long tickets, long period, long current_ts)
{ {
r->tickets = tickets; r->tickets = tickets;
r->scaled_tickets = tickets * SCALE_FACTOR; r->ticket_cost = tickets * period;
r->period = period; r->period = period;
r->available_tickets = r->scaled_tickets; r->funds = r->ticket_cost * tickets;
r->last_ts = current_ts; r->last_ts = current_ts;
} }
void uc_ratelimit_reset(struct RateLimit *r, long current_ts) void uc_ratelimit_reset(struct RateLimit *r, long current_ts)
{ {
r->available_tickets = r->scaled_tickets; r->funds = r->ticket_cost * r->tickets;
if (current_ts) if (current_ts)
r->last_ts = current_ts; r->last_ts = current_ts;
@@ -36,22 +33,21 @@ int uc_ratelimit_allow(struct RateLimit *r, long current_ts)
diff_period = 0; diff_period = 0;
} }
//Calculate the number of tickets that became available since //Calculate the cost of tickets that became available since
//the last time. //the last time.
r->available_tickets += diff_period * r->funds += diff_period *
(r->scaled_tickets / r->period); ((r->tickets * r->ticket_cost) / r->period);
//throttle handing out tickets //throttle handing out tickets
// if (r->funds > r->ticket_cost * r->tickets)
if (r->available_tickets > r->scaled_tickets) r->funds = r->ticket_cost * r->tickets;
r->available_tickets = r->scaled_tickets;
//If we have at least one ticket, we can allow //If we have enough to buy atleast one ticket, we can allow
if (r->available_tickets >= 1 * SCALE_FACTOR) { if (r->funds >= r->ticket_cost) {
r->available_tickets -= 1 * SCALE_FACTOR; r->funds -= r->ticket_cost;
allowed = 1; allowed = 1;
} else { } else {
//no more tickets. //not enough to buy a ticket
allowed = 0; allowed = 0;
} }