-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcachedmap.go
158 lines (144 loc) · 3.58 KB
/
cachedmap.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
150
151
152
153
154
155
156
157
158
package cachedmap
import (
"sync"
"sync/atomic"
"time"
"github.com/sirupsen/logrus"
)
type CachedMap struct {
Name string
Hits int64
Misses int64
Writes int64
Flushes int64
MaxLength int
entries map[string]CacheEntry
lock sync.RWMutex
keyTimeout time.Duration
flushCycle time.Duration
log *logrus.Entry
}
type CacheEntry struct {
Data interface{}
RemoveTime time.Time
}
type Stats struct {
Name string `json:"name"`
Hits int64 `json:"hits"`
Misses int64 `json:"misses"`
Writes int64 `json:"writes"`
Flushes int64 `json:"flushes"`
MaxLength int `json:"max_length"`
Length int `json:"length"`
KeyTTL int64 `json:"key_ttl"`
FlushCycle int64 `json:"flush_cycle"`
}
/* TS definition:
export class CachedMapStats {
name: string;
hits: number;
misses: number;
writes: number;
flushes: number;
max_length: number;
length: number;
key_ttl: number;
flush_cycle: number;
}
*/
func NewCachedMap(name string, keyTimeout, flushCycle time.Duration, log *logrus.Entry) *CachedMap {
cm := &CachedMap{
Name: name,
keyTimeout: keyTimeout,
flushCycle: flushCycle,
entries: make(map[string]CacheEntry, 100),
}
cm.SetLog(log)
cm.flusher()
return cm
}
// Tidy starts a go routine which will periodically drop the entire cache.
func (c *CachedMap) flusher() {
go func() {
for {
time.Sleep(c.flushCycle)
c.lock.Lock()
oldEntries := c.entries
c.entries = make(map[string]CacheEntry, 10+len(c.entries))
c.lock.Unlock()
c.Flushes++
if len(oldEntries) > c.MaxLength {
c.MaxLength = len(oldEntries)
}
if c.log != nil {
c.log.Info(c.GetStats())
}
}
}()
}
// Set adds an item to the map with a computed remove time.
// It returns the remove time in case you want to use it in a
// nearby SetUntil() call.
func (c *CachedMap) Set(key string, data interface{}) time.Time {
removeTime := time.Now().Add(c.keyTimeout)
c.SetUntil(key, data, removeTime)
return removeTime
}
// SetUntil adds an item to the map with the given remove time.
// This allows you to override the default key timeout.
func (c *CachedMap) SetUntil(key string, data interface{}, removeTime time.Time) {
e := CacheEntry{
Data: data,
RemoveTime: removeTime,
}
c.lock.Lock()
c.entries[key] = e
c.Writes++
c.lock.Unlock()
}
// Len returns the number of items in the map.
func (c *CachedMap) Len() int {
return len(c.entries)
}
// Get returns a non-expired entry and true.
// If no valid entry is found, it returns (nil, false).
func (c *CachedMap) Get(key string) (interface{}, bool) {
c.lock.RLock()
e, ok := c.entries[key]
c.lock.RUnlock()
if !ok || time.Now().After(e.RemoveTime) {
atomic.AddInt64(&c.Misses, 1)
return nil, false
}
atomic.AddInt64(&c.Hits, 1)
return e.Data, true
}
// GetStats returns current stats in a convenient, loggable, struct.
func (c *CachedMap) GetStats() Stats {
// MaxLength is only set at flush time so we might need to update it here.
l := c.Len()
m := c.MaxLength
if l > m {
m = l
}
return Stats{
Name: c.Name,
Hits: c.Hits,
Misses: c.Misses,
Writes: c.Writes,
Flushes: c.Flushes,
MaxLength: m,
Length: l,
KeyTTL: int64(c.keyTimeout / time.Second),
FlushCycle: int64(c.flushCycle / time.Second),
}
}
// SetLog sets the logger used by this component and adds a component identifier.
// The instance name will be logged via the GetStats() call.
func (c *CachedMap) SetLog(log *logrus.Entry) {
if log == nil {
c.log = nil
return
}
c.log = log.WithField("component", "cachedmap")
}