ratelimit

package
v0.19.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 11, 2023 License: MIT Imports: 8 Imported by: 0

README

Rate Limit

Warning: this is still a work in progress. Tests succeeding != production ready.

Rate limiting is enabled by default in a default Equinox client. For now the only store available is in-memory, though I want to add Redis support in the future, maybe using a lua script.

Info on rate limiting:

How does it work

Bucket

A limit for the App or Method is a bucket of tokens:

type Bucket struct {
	// Time interval in seconds
	interval time.Duration
	// Maximum number of tokens
	limit int
	// Current number of tokens, starts at limit
	tokens int
	// Next reset
	next  time.Time
	mutex sync.Mutex
}

When creating a bucket, interval is the time in seconds between resets, limit is the maximum number of tokens, and tokens is the current number of tokens.

func NewBucket(interval time.Duration, limit int, tokens int) *Bucket {
	return &Bucket{
		interval: interval * time.Second,
		limit:    limit,
		tokens:   tokens,
		next:     time.Now().Add(interval * time.Second),
		mutex:    sync.Mutex{},
	}
}

When initializing a bucket, the current amount of tokens will be the same as the limit minus the current count provided from the X-App-Rate-Limit-Count or X-Method-Rate-Limit-Count headers.

func parseHeaders(limitHeader string, countHeader string) []*Bucket {
	if limitHeader == "" || countHeader == "" {
		return []*Bucket{}
	}
	limits := strings.Split(limitHeader, ",")
	counts := strings.Split(countHeader, ",")
	rates := make([]*Bucket, len(limits))
	for i := range limits {
		limit, seconds := getNumbersFromPair(limits[i])
		count, _ := getNumbersFromPair(counts[i])
		rates[i] = NewBucket(time.Duration(seconds), limit, limit-count)
	}
	return rates
}
  • When a bucket is full, the amount of tokens will be the same as the limit - Not Rate limited, able to Take() a token from the bucket.
  • When a bucket is empty, the amount of tokens will be 0 - Rate limited, not able to Take() a token without waiting.
Take

After creating a request and checking if it was cached, the client will use Take(), initializing the App and Method buckets in a route AND the MethodID if not initialized.

If rate limited, Take() will block until the next bucket reset. A context can be passed, allowing for cancellation and checking if the deadline will be exceeded, cancelling the block if it will.

Take() will then decrease tokens for the App and Method in the buckets by one.

Update

After receiving a response, Update() will verify that the current buckets in-memory match the ones received by the Riot API, if they don't, it will force an update in all buckets.

By 'matching', I mean that the current limit and interval in these buckets match the ones received by the Riot API.

Documentation

Index

Constants

View Source
const (
	RATE_LIMIT_TYPE_HEADER = "X-Rate-Limit-Type"
	RETRY_AFTER_HEADER     = "Retry-After"

	APP_RATE_LIMIT_HEADER          = "X-App-Rate-Limit"
	APP_RATE_LIMIT_COUNT_HEADER    = "X-App-Rate-Limit-Count"
	METHOD_RATE_LIMIT_HEADER       = "X-Method-Rate-Limit"
	METHOD_RATE_LIMIT_COUNT_HEADER = "X-Method-Rate-Limit-Count"

	APP_RATE_LIMIT_TYPE     = "application"
	METHOD_RATE_LIMIT_TYPE  = "method"
	SERVICE_RATE_LIMIT_TYPE = "service"
)

Variables

View Source
var (
	Err429ButNoRetryAfterHeader = errors.New("received 429 but no Retry-After header was found")
	ErrContextDeadlineExceeded  = errors.New("waiting would exceed context deadline")
)

Functions

func WaitN

func WaitN(ctx context.Context, estimated time.Time, duration time.Duration) error

WaitN waits for the given duration after checking if the context deadline will be exceeded.

Types

type Bucket

type Bucket struct {
	// contains filtered or unexported fields
}

func NewBucket

func NewBucket(interval time.Duration, limit int, tokens int) *Bucket

func (*Bucket) MarshalZerologObject

func (b *Bucket) MarshalZerologObject(encoder *zerolog.Event)

type Limit

type Limit struct {
	// contains filtered or unexported fields
}

Limit represents a collection of buckets and the type of limit (application or method).

func NewLimit

func NewLimit(limitType string) *Limit

type Limits

type Limits struct {
	App     *Limit
	Methods map[string]*Limit
}

Limits in a region.

func NewLimits

func NewLimits() *Limits

type RateLimit

type RateLimit struct {
	Region  map[any]*Limits
	Enabled bool
	// contains filtered or unexported fields
}

func NewInternalRateLimit added in v0.19.3

func NewInternalRateLimit() *RateLimit

func (*RateLimit) CheckRetryAfter

func (r *RateLimit) CheckRetryAfter(route any, methodID string, headers http.Header) (time.Duration, error)

func (*RateLimit) Take

func (r *RateLimit) Take(ctx context.Context, logger zerolog.Logger, route any, methodID string) error

Take decreases tokens for the App and Method rate limit buckets in a route by one.

If rate limited, will block until the next bucket reset.

func (*RateLimit) Update

func (r *RateLimit) Update(logger zerolog.Logger, route any, methodID string, headers http.Header)

Update creates new buckets in a route with the limits provided in the response headers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL