Raw
  1package ratelimit
  2
  3import (
  4	"container/list"
  5	"errors"
  6	"net/netip"
  7	"sync"
  8	"time"
  9)
 10
 11type Rate struct {
 12	Burst int
 13	Every time.Duration
 14}
 15
 16type entry[K comparable] struct {
 17	key    K
 18	tokens float64
 19	at     time.Time
 20}
 21
 22var errNoEntries = errors.New("ratelimit: a table needs at least one entry")
 23
 24type Table[K comparable] struct {
 25	mu      sync.Mutex
 26	now     func() time.Time
 27	max     int
 28	entries map[K]*list.Element
 29	order   *list.List
 30	evicted uint64
 31}
 32
 33func New[K comparable](max int, now func() time.Time) *Table[K] {
 34	if max < 1 {
 35		panic(errNoEntries)
 36	}
 37	return &Table[K]{now: now, max: max, entries: map[K]*list.Element{}, order: list.New()}
 38}
 39
 40func (t *Table[K]) bucket(k K, rate Rate, now time.Time) *entry[K] {
 41	el, ok := t.entries[k]
 42	if !ok {
 43		el = t.order.PushFront(&entry[K]{key: k, tokens: float64(rate.Burst), at: now})
 44		t.entries[k] = el
 45		for t.order.Len() > t.max {
 46			oldest := t.order.Back()
 47			t.order.Remove(oldest)
 48			delete(t.entries, oldest.Value.(*entry[K]).key)
 49			t.evicted++
 50		}
 51	}
 52	t.order.MoveToFront(el)
 53	e := el.Value.(*entry[K])
 54	if elapsed := now.Sub(e.at); elapsed > 0 {
 55		e.tokens = min(float64(rate.Burst), e.tokens+float64(elapsed)/float64(rate.Every))
 56	}
 57	e.at = now
 58	return e
 59}
 60
 61func wait(tokens float64, rate Rate) time.Duration {
 62	return time.Duration((1 - tokens) * float64(rate.Every))
 63}
 64
 65func (t *Table[K]) Take(k K, rate Rate) (time.Duration, bool) {
 66	if rate.Burst <= 0 || rate.Every <= 0 {
 67		return 0, true
 68	}
 69	t.mu.Lock()
 70	defer t.mu.Unlock()
 71	e := t.bucket(k, rate, t.now())
 72	if e.tokens >= 1 {
 73		e.tokens--
 74		return 0, true
 75	}
 76	return wait(e.tokens, rate), false
 77}
 78
 79func (t *Table[K]) Allowed(k K, rate Rate) (time.Duration, bool) {
 80	if rate.Burst <= 0 || rate.Every <= 0 {
 81		return 0, true
 82	}
 83	t.mu.Lock()
 84	defer t.mu.Unlock()
 85	if _, ok := t.entries[k]; !ok {
 86		return 0, true
 87	}
 88	e := t.bucket(k, rate, t.now())
 89	if e.tokens >= 1 {
 90		return 0, true
 91	}
 92	return wait(e.tokens, rate), false
 93}
 94
 95func (t *Table[K]) Len() int {
 96	t.mu.Lock()
 97	defer t.mu.Unlock()
 98	return t.order.Len()
 99}
100
101func Prefix(a netip.Addr) netip.Prefix {
102	if !a.IsValid() {
103		return netip.Prefix{}
104	}
105	a = a.WithZone("").Unmap()
106	bits := 32
107	if a.Is6() {
108		bits = 64
109	}
110	p, _ := a.Prefix(bits)
111	return p
112}