File: cache.go

package info (click to toggle)
golang-github-ulule-limiter 3.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 240 kB
  • sloc: sh: 114; makefile: 15
file content (159 lines) | stat: -rw-r--r-- 3,620 bytes parent folder | download
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
150
151
152
153
154
155
156
157
158
159
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: 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()
}

// Reset changes the key's value and resets the expiration.
func (cache *Cache) Reset(key string, duration time.Duration) (int64, time.Time) {
	cache.mutex.Lock()
	delete(cache.counters, key)
	cache.mutex.Unlock()

	expiration := time.Now().Add(duration).UnixNano()
	return 0, time.Unix(0, expiration)
}