summaryrefslogtreecommitdiffstats
path: root/vendor/gopkg.in/throttled/throttled.v1/store/mem.go
blob: 22d200e8d1fb5e04fd108d82ed8f93934762ea43 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package store

import (
	"sync"
	"time"

	"github.com/golang/groupcache/lru"
	"gopkg.in/throttled/throttled.v1"
)

// memStore implements an in-memory Store.
type memStore struct {
	sync.Mutex
	keys *lru.Cache
	m    map[string]*counter
}

// NewMemStore creates a new MemStore. If maxKeys > 0, the number of different keys
// is restricted to the specified amount. In this case, it uses an LRU algorithm to
// evict older keys to make room for newer ones. If a request is made for a key that
// has been evicted, it will be processed as if its count was 0, possibly allowing requests
// that should be denied.
//
// If maxKeys <= 0, there is no limit on the number of keys, which may use an unbounded amount of
// memory depending on the server's load.
//
// The MemStore is only for single-process rate-limiting. To share the rate limit state
// among multiple instances of the web server, use a database- or key-value-based
// store.
//
func NewMemStore(maxKeys int) throttled.Store {
	var m *memStore
	if maxKeys > 0 {
		m = &memStore{
			keys: lru.New(maxKeys),
		}
	} else {
		m = &memStore{
			m: make(map[string]*counter),
		}
	}
	return m
}

// A counter represents a single entry in the MemStore.
type counter struct {
	n  int
	ts time.Time
}

// Incr increments the counter for the specified key. It returns the new
// count value and the remaining number of seconds, or an error.
func (ms *memStore) Incr(key string, window time.Duration) (int, int, error) {
	ms.Lock()
	defer ms.Unlock()
	var c *counter
	if ms.keys != nil {
		v, _ := ms.keys.Get(key)
		if v != nil {
			c = v.(*counter)
		}
	} else {
		c = ms.m[key]
	}
	if c == nil {
		c = &counter{0, time.Now().UTC()}
	}
	c.n++
	if ms.keys != nil {
		ms.keys.Add(key, c)
	} else {
		ms.m[key] = c
	}
	return c.n, throttled.RemainingSeconds(c.ts, window), nil
}

// Reset resets the counter for the specified key. It sets the count
// to 1 and initializes the timestamp with the current time, in UTC.
// It returns an error if the operation fails.
func (ms *memStore) Reset(key string, win time.Duration) error {
	ms.Lock()
	defer ms.Unlock()
	c := &counter{1, time.Now().UTC()}
	if ms.keys != nil {
		ms.keys.Add(key, c)
	} else {
		ms.m[key] = c
	}
	return nil
}