forked from ulule/limiter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.go
149 lines (120 loc) · 3.23 KB
/
cache.go
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package memory
import (
"runtime"
"sync"
"time"
)
// Forked from https://github.com/patrickmn/go-cache
// CacheWrapper is used to ensure that the underlying cleaner goroutine used to clean expired keys will not prevent
// Cache from being garbage collected.
type CacheWrapper struct {
*Cache
}
// A cleaner will periodically delete expired keys from cache.
type cleaner struct {
interval time.Duration
stop chan bool
}
// Run will periodically delete expired keys from given cache until GC notify that it should stop.
func (cleaner *cleaner) Run(cache *Cache) {
ticker := time.NewTicker(cleaner.interval)
for {
select {
case <-ticker.C:
cache.Clean()
case <-cleaner.stop:
ticker.Stop()
return
}
}
}
// stopCleaner is a callback from GC used to stop cleaner goroutine.
func stopCleaner(wrapper *CacheWrapper) {
wrapper.cleaner.stop <- true
}
// startCleaner will start a cleaner goroutine for given cache.
func startCleaner(cache *Cache, interval time.Duration) {
cleaner := &cleaner{
interval: interval,
stop: make(chan bool),
}
cache.cleaner = cleaner
go cleaner.Run(cache)
}
// Counter is a simple counter with an optional expiration.
type Counter struct {
Value int64
Expiration int64
}
// Expired returns true if the counter has expired.
func (counter Counter) Expired() bool {
if counter.Expiration == 0 {
return false
}
return time.Now().UnixNano() > counter.Expiration
}
// Cache contains a collection of counters.
type Cache struct {
mutex sync.RWMutex
counters map[string]Counter
cleaner *cleaner
}
// NewCache returns a new cache.
func NewCache(cleanInterval time.Duration) *CacheWrapper {
cache := &Cache{
counters: map[string]Counter{},
}
wrapper := &CacheWrapper{cache}
if cleanInterval > 0 {
startCleaner(cache, cleanInterval)
runtime.SetFinalizer(wrapper, stopCleaner)
}
return wrapper
}
// Increment increments given value on key.
// If key is undefined or expired, it will create it.
func (cache *Cache) Increment(key string, value int64, duration time.Duration) (int64, time.Time) {
cache.mutex.Lock()
counter, ok := cache.counters[key]
if !ok || counter.Expired() {
expiration := time.Now().Add(duration).UnixNano()
counter = Counter{
Value: value,
Expiration: expiration,
}
cache.counters[key] = counter
cache.mutex.Unlock()
return value, time.Unix(0, expiration)
}
value = counter.Value + value
counter.Value = value
expiration := counter.Expiration
cache.counters[key] = counter
cache.mutex.Unlock()
return value, time.Unix(0, expiration)
}
// Get returns key's value and expiration.
func (cache *Cache) Get(key string, duration time.Duration) (int64, time.Time) {
cache.mutex.RLock()
counter, ok := cache.counters[key]
if !ok || counter.Expired() {
expiration := time.Now().Add(duration).UnixNano()
cache.mutex.RUnlock()
return 0, time.Unix(0, expiration)
}
value := counter.Value
expiration := counter.Expiration
cache.mutex.RUnlock()
return value, time.Unix(0, expiration)
}
// Clean will deleted any expired keys.
func (cache *Cache) Clean() {
now := time.Now().UnixNano()
cache.mutex.Lock()
for key, counter := range cache.counters {
if now > counter.Expiration {
delete(cache.counters, key)
}
}
cache.mutex.Unlock()
}