rate

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jan 20, 2025 License: MIT Imports: 2 Imported by: 0

README

rate Go Reference

An implementation of a buffered and unbuffered limiter to control the frequency of operations over time. The limiters will grant or deny permission at the rate provided when creating them.

Installation

go get github.com/yisroelshulman/rate

Features

  • concurrent usage
  • non-concurrent usage

Usage

The rate package contains 2 types of limiters:

  • BufferedLimiter
  • UnbufferedLimiter

While the general functionality is the same they each have some unique behaviors which will be useful depending on what the limiter is needed for.

Once the rate package was installed (verify in the go.mod file) it is ready to be imported for use.

// imports
import (
    "time"
    "github.com/yisroelshulman/rate"
)


// declare and initialize the limiters
var limiter *rate.UnbufferedLimiter
var bufLimiter *rate.BufferedLimiter
limiter = NewUnbufferedLimiter(2, time.Second)
bufLimiter = NewBufferedLimiter(1, 5, time.Second)

The above code will create 2 limiters a *UnbufferedLimiter with a rate of 2 permissions per second, and a *BufferedLimiter with a rate of 1 permission per second and a buffer with a max capacity of 5.

Using the limiters

func main() {
//... prior code

 err := operation(limiter, timeout)
 if err != nil {
    // do something
 }
 err = op(bufLimiter, timeout)
 if err != nil {
    // do something
 }

// more code ...
}

func operation(limiter *rate.UnbufferedLimiter, timeout *time.Duration) error{
    err := limiter.Wait(timeout)
    if err != nil {
        // request timed out
        return err
    }
    doOperation()
    return nil
}

func op(limiter *rate.BufferedLimiter, timeout *time.Duration) error {
    err := limiter.Wait(timeout)
    if err != nil {
        switch err.(type) {
            case *LimiterWaitTimedOutError:
                // do something if needed
                return err
            case *LimiterBufferFullError:
                // do something if needed
                return err
            default:
                // something is wrong
                return someErr
        }
    }
    doOperation()
    return err
}

It is important to note that Wait(timeout) is a blocking function and only returns when permission is granted or the request error either by timeout or the buffer is full.

Only the UnbufferedLimiter supports non-blocking request.

func (limiter *UnbufferedLimiter, timeout *time.Duration) {
    timeRemaining, err := limiter.TryWait(timeout)
    if err != nil { // the rate has been reached
        // do something ex.
        return
    }
    doOperation()
}

The TryWait(timeout) function does not block and returns an error if the limiter has reached the limit and the time remaining until the next permission can be granted.

Note: Although both limiters can be used for both concurrent and non-concurrent uses it is recommended to use the UnbufferedLimiter for non-concurrent use cases to reduce resource usage. and the BufferdLimiter for concurrent uses to prevent request starvation.

Since wait is a blocking function calling wait concurrently on a UnbufferedLimiter may lead to request starvation as there is no way to guarantee the order in which permission is granted. And the BufferedLimiter grants permission in the order the requests were put in the buffer. The BufferedLimiter is SUBJECT TO RACE CONDITIONS due to the time delta from the approval and returning from Wait.

Tests

The tests take < 30 seconds

Contributing

I consider this project feature complete.

Reported issues will be looked at for bug fixes.

Documentation

Overview

Package rate implements rate limiters to control the frequency of operations over time. The limiter will grant or deny permissions enforcing the provided rate.

The package contains two types of limiters the buffered limiter and the unbuffered limiter. The buffered limiter has an internal buffer that ensures that concurrent requests for permissions are granted in the order that they were received by the limiter. The unbuffered limiter also has a non-blocking option.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BufferedLimiter

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

A BufferedLimiter is a limiter with an internal buffer that limits permissions at a given rate and time interval

func NewBufferedLimiter

func NewBufferedLimiter(rate, capacity int, interval time.Duration) *BufferedLimiter

NewBufferedLimiter returns a new BufferedLimiter given a capacity, rate, and interval.

The BufferedLimiter will limit permissions at the rate per time interval sending additional requests to the buffer for later processing. The purpose of the buffer is to prevent starvation of requests.

If the received rate <= 0, the default rate of 1 will be used, likewise if a capacity <= to 0 the capacity for the buffer will be set to 1. If the interval received is <= 0 the time interval will default to 1 millisecond. This is to prevent the BufferedLimiter from erroring during use and to ensure the caller gets a working limiter without checking for errors.

func (*BufferedLimiter) Wait

func (l *BufferedLimiter) Wait(timeout *time.Duration) error

Wait returns when the limiter grants permission or times out.

The Wait receiver for the BufferedLimiter blocks until the permission is granted or the request times out. When permission is granted, a nil value is returned. Otherwise, an error will be returned depending on what caused the limiter to deny permission, (LimiterBufferFull, LimiterWaitTimedOut).

Calls to Wait are thread safe in the sense that the limiter won't break and will enforce the rate per interval regardless of how many threads share the limiter. The BufferedLimiter ensures that approvals are granted in the order the limiter received the requests for permission, (with timed out requests being ignored), preventing request starvation. This does not guarantee that the execution of whatever was limited will be in order only that the permissions are granted in order. Of course, there are no guarantees when a LimiterBufferFull error is returned.

type LimiterBufferFullError added in v1.2.0

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

LimiterBufferFullError is the error returned when the buffer of the bufferedlimiter is full

func (*LimiterBufferFullError) Error added in v1.2.0

func (l *LimiterBufferFullError) Error() string

type LimiterOverLimitError added in v1.2.0

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

LimiterOverLimitError is the error returned when the unbufferedlimiter.TryWait fails

func (*LimiterOverLimitError) Error added in v1.2.0

func (l *LimiterOverLimitError) Error() string

type LimiterWaitTimedOutError added in v1.2.0

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

LimiterWaitTimedOutError is the error returned when limiter.Wait times out

func (*LimiterWaitTimedOutError) Error added in v1.2.0

func (l *LimiterWaitTimedOutError) Error() string

type UnbufferedLimiter

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

An UnbufferdLimiter is a limiter without an internal buffer that limits permission at a given rate and time interval

func NewUnbufferedLimiter

func NewUnbufferedLimiter(rate int, interval time.Duration) *UnbufferedLimiter

NewUnbufferedLimiter returns a new UnbufferedLimiter given a rate and a time interval.

The Unbufferedlimiter will limit permissions at the provided rate for the given time interval.

If the rate received <= 0 the rate will default to 1 and if the received interval <= 0 it will be set to 1 millisecond. This is because the Unbufferedlimiter must have a non-zero rate and interval for simplicity and ease of use to prevent the UnbufferedLimiter from erroring during use and not have the NewUnbufferedLimiter function return an error.

func (*UnbufferedLimiter) TryWait

func (l *UnbufferedLimiter) TryWait() (time.Duration, error)

TryWait returns whether or not the Unbufferedlimiter granted permission.

The TryWait receiver is non-blocking and returns immediately. If permission is granted the error is nil. If permission is not granted a LimiterOverLimit error is returned and the time duration until the next potential approval can occur.

TryWait is a threadsafe function allowing multiple threads to share the same Unbufferedlimiter.

func (*UnbufferedLimiter) Wait

func (l *UnbufferedLimiter) Wait(timeout *time.Duration) error

Wait returns when the limiter grants permission or times out.

The Wait receiver on the UnbufferedLimiter blocks until permission is granted or the request times out. When permission is granted a nil value is returned and when the request times out a LimiterWaitTimedOut error is returned.

Additionally it is thread safe in the sense that the limiter will still enforce the rate regardless of how many threads share the same Unbufferedlimiter. However, there is no guarantee as to which order the Unbufferedlimiter will grant permission or that permission will ever be granted if there are a large number of requests (request starvation).

Jump to

Keyboard shortcuts

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