@@ -9,101 +9,153 @@ import (
9
9
type cacheEntry struct {
10
10
entry []byte
11
11
hits int
12
+ age int64
12
13
}
13
14
14
15
type SimpleCache struct {
15
- maxItems int
16
- items map [string ]cacheEntry
17
- lock sync.Mutex
16
+ maxItems int
17
+ items map [string ]cacheEntry
18
+ lock sync.Mutex
19
+ getUnix func () int64
20
+ ageOutTimeSeconds int64
18
21
}
19
22
20
- func NewSimpleCache (maxItems int ) * SimpleCache {
23
+ func NewSimpleCache (maxItems int , ageOutTimeSeconds int64 ) * SimpleCache {
21
24
simpleCache := SimpleCache {
22
25
maxItems : maxItems ,
23
26
items : map [string ]cacheEntry {},
24
27
lock : sync.Mutex {},
28
+ getUnix : func () int64 {
29
+ return time .Now ().Unix ()
30
+ },
31
+ ageOutTimeSeconds : ageOutTimeSeconds ,
25
32
}
26
33
simpleCache .runAgeItems ()
27
34
return & simpleCache
28
35
}
29
36
30
- func (cache * SimpleCache ) runAgeItems () {
37
+ func (c * SimpleCache ) runAgeItems () {
31
38
go func () {
32
- for {
33
- // maps are randomly ordered, so only decrementing 50 at a time should be acceptable
34
- count := 50
35
- cache .lock .Lock ()
36
- for k , v := range cache .items {
37
- if v .hits > 0 {
38
- v .hits --
39
- cache .items [k ] = v
40
- }
41
- count --
42
- if count <= 0 {
43
- break
44
- }
45
- }
46
- cache .lock .Unlock ()
47
- time .Sleep (10 * time .Second )
48
- }
39
+ time .Sleep (10 * time .Second )
40
+ c .adjustLfu ()
41
+ c .ageOut ()
49
42
}()
50
43
}
51
44
52
- func (cache * SimpleCache ) Add (cacheKey string , entry []byte ) {
53
- cache .expireItems ()
45
+ func (c * SimpleCache ) adjustLfu () {
46
+ c .lock .Lock ()
47
+ defer c .lock .Unlock ()
48
+
49
+ // maps are randomly ordered, so only decrementing 50 at a time should be acceptable
50
+ count := 50
54
51
55
- cache .lock .Lock ()
56
- defer cache .lock .Unlock ()
52
+ for k , v := range c .items {
53
+ if v .hits > 0 {
54
+ v .hits --
55
+ c .items [k ] = v
56
+ }
57
+ count --
58
+ if count <= 0 {
59
+ break
60
+ }
61
+ }
62
+ }
57
63
58
- cache .items [cacheKey ] = cacheEntry {
64
+ func (c * SimpleCache ) Add (cacheKey string , entry []byte ) {
65
+ c .evictItems ()
66
+
67
+ c .lock .Lock ()
68
+ defer c .lock .Unlock ()
69
+
70
+ c .items [cacheKey ] = cacheEntry {
59
71
entry : entry ,
60
72
hits : 1 ,
73
+ age : c .getUnix (),
61
74
}
62
75
}
63
76
64
- func (cache * SimpleCache ) Get (cacheKey string ) ([]byte , bool ) {
65
- cache .lock .Lock ()
66
- defer cache .lock .Unlock ()
77
+ func (c * SimpleCache ) Get (cacheKey string ) ([]byte , bool ) {
78
+ c .lock .Lock ()
79
+ defer c .lock .Unlock ()
67
80
68
- item , ok := cache .items [cacheKey ]
81
+ item , ok := c .items [cacheKey ]
69
82
70
83
if ok {
71
84
if item .hits < 100 {
72
85
item .hits ++
73
86
}
74
- cache .items [cacheKey ] = item
87
+ c .items [cacheKey ] = item
75
88
return item .entry , true
76
89
}
77
90
78
91
return nil , false
79
92
}
80
93
81
- // ExpireItems is called before any insert operation because we need to ensure we have less than
94
+ // evictItems is called before any insert operation because we need to ensure we have less than
82
95
// the total number of items
83
- func (cache * SimpleCache ) expireItems () {
84
- cache .lock .Lock ()
85
- defer cache .lock .Unlock ()
96
+ func (c * SimpleCache ) evictItems () {
97
+ c .lock .Lock ()
98
+ defer c .lock .Unlock ()
99
+
100
+ // insert process only needs to expire if we have too much
101
+ // as such if we haven't hit the limit return
102
+ if len (c .items ) < c .maxItems {
103
+ return
104
+ }
86
105
87
106
count := 10
88
- if len (cache .items ) >= cache .maxItems {
89
- lfuKey := ""
90
- lfuLowestCount := math .MaxInt
91
107
92
- for k , v := range cache .items {
93
- v .hits --
94
- cache .items [k ] = v
95
- if v .hits < lfuLowestCount {
96
- lfuKey = k
97
- lfuLowestCount = v .hits
98
- }
99
-
100
- // we only want to process X random elements so we don't spin forever
101
- count --
102
- if count <= 0 {
103
- break
104
- }
108
+ lfuKey := ""
109
+ lfuLowestCount := math .MaxInt
110
+
111
+ for k , v := range c .items {
112
+ v .hits --
113
+ c .items [k ] = v
114
+ if v .hits < lfuLowestCount {
115
+ lfuKey = k
116
+ lfuLowestCount = v .hits
105
117
}
106
118
107
- delete (cache .items , lfuKey )
119
+ // we only want to process X random elements so we don't spin forever
120
+ // however we also exit if the count is <= 0
121
+ count --
122
+ if count <= 0 || lfuLowestCount <= 0 {
123
+ break
124
+ }
125
+ }
126
+
127
+ delete (c .items , lfuKey )
128
+ }
129
+
130
+ // ageOut is called on a schedule to evict the oldest entry so long as
131
+ // its older than the configured cache time
132
+ func (c * SimpleCache ) ageOut () {
133
+ // we also want to age out things eventually to avoid https://github.com/boyter/scc/discussions/435
134
+ // as such loop though and the first one that's older than a day with 0 hits is removed
135
+
136
+ c .lock .Lock ()
137
+ defer c .lock .Unlock ()
138
+
139
+ count := 10
140
+ lfuKey := ""
141
+ lfuOldest := int64 (math .MaxInt )
142
+
143
+ // maps are un-ordered so this is acceptable
144
+ for k , v := range c .items {
145
+ c .items [k ] = v
146
+ if v .age < lfuOldest {
147
+ lfuKey = k
148
+ lfuOldest = v .age
149
+ }
150
+
151
+ count --
152
+ if count <= 0 {
153
+ break
154
+ }
155
+ }
156
+
157
+ // evict the oldest but only if its older than it should be
158
+ if lfuOldest <= c .getUnix ()- c .ageOutTimeSeconds {
159
+ delete (c .items , lfuKey )
108
160
}
109
161
}
0 commit comments