Skip to content

Commit 07c8e28

Browse files
committed
migrate from private production repo to github
1 parent 41229fd commit 07c8e28

File tree

2 files changed

+541
-0
lines changed

2 files changed

+541
-0
lines changed

ramcache.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// Package ramcache implements an in-memory key/value cache with
2+
// expirations based on access and insertion times. It is safe
3+
// for concurrent use by multiple goroutines.
4+
package ramcache // import "stathat.com/c/ramcache"
5+
6+
import (
7+
"container/heap"
8+
"errors"
9+
"fmt"
10+
"sync"
11+
"time"
12+
)
13+
14+
// ErrNotFound is returned when a key isn't found in the cache.
15+
var ErrNotFound = errors.New("ramcache: key not found in cache")
16+
17+
// Ramcache is an in-memory key/value store. It has two
18+
// configuration durations: TTL (time to live) and MaxAge.
19+
// Ramcache removes any objects that haven't been accessed in the
20+
// TTL duration.
21+
// Ramcache removes (on get) any objects that were created more
22+
// than MaxAge time ago.
23+
// This allows you to keep recently accessed objects cached but
24+
// also delete them once they have been in the cache for MaxAge
25+
// duration.
26+
type Ramcache struct {
27+
cache map[string]*item
28+
tqueue timeQueue
29+
TTL time.Duration
30+
MaxAge time.Duration
31+
frozen bool
32+
sync.RWMutex
33+
}
34+
35+
// New creates a Ramcache with a TTL of 5 minutes. You can change
36+
// this by setting the result's TTL to any time.Duration you want.
37+
// You can also set the MaxAge on the result.
38+
func New() *Ramcache {
39+
tq := make(timeQueue, 0)
40+
c := make(map[string]*item)
41+
r := &Ramcache{cache: c, tqueue: tq, TTL: 5 * time.Minute}
42+
go r.cleanup()
43+
return r
44+
}
45+
46+
// Get retrieves a value from the cache.
47+
func (rc *Ramcache) Get(key string) (interface{}, error) {
48+
rc.Lock()
49+
defer rc.Unlock()
50+
i, ok := rc.cache[key]
51+
if !ok {
52+
return nil, ErrNotFound
53+
}
54+
if rc.MaxAge > 0 && time.Since(i.createdAt) > rc.MaxAge {
55+
fmt.Printf("deleting item %s. Created at: %s (%s ago).\n", key, i.createdAt, time.Since(i.createdAt))
56+
heap.Remove(&rc.tqueue, i.index)
57+
delete(rc.cache, key)
58+
return nil, ErrNotFound
59+
}
60+
61+
rc.tqueue.Access(i)
62+
return i.value, nil
63+
}
64+
65+
// GetNoAccess retrieves a value from the cache, but does not
66+
// update the access time.
67+
func (rc *Ramcache) GetNoAccess(key string) (interface{}, error) {
68+
rc.Lock()
69+
defer rc.Unlock()
70+
i, ok := rc.cache[key]
71+
if !ok {
72+
return nil, ErrNotFound
73+
}
74+
if rc.MaxAge > 0 && time.Since(i.createdAt) > rc.MaxAge {
75+
fmt.Printf("deleting item %s. Created at: %s (%s ago).\n", key, i.createdAt, time.Since(i.createdAt))
76+
heap.Remove(&rc.tqueue, i.index)
77+
delete(rc.cache, key)
78+
return nil, ErrNotFound
79+
}
80+
return i.value, nil
81+
}
82+
83+
// Set inserts a value in the cache. If an object already exists,
84+
// it will be replaced, but the createdAt timestamp won't change.
85+
func (rc *Ramcache) Set(key string, obj interface{}) error {
86+
rc.Lock()
87+
defer rc.Unlock()
88+
i := newItem(key, obj)
89+
existing, ok := rc.cache[key]
90+
if ok {
91+
heap.Remove(&rc.tqueue, existing.index)
92+
i.createdAt = existing.createdAt
93+
}
94+
rc.cache[key] = i
95+
rc.tqueue.Insert(i)
96+
return nil
97+
}
98+
99+
// Delete deletes an item from the cache.
100+
func (rc *Ramcache) Delete(key string) error {
101+
rc.Lock()
102+
defer rc.Unlock()
103+
i, ok := rc.cache[key]
104+
if !ok {
105+
return ErrNotFound
106+
}
107+
heap.Remove(&rc.tqueue, i.index)
108+
delete(rc.cache, key)
109+
return nil
110+
}
111+
112+
// Remove deletes an item from the cache and returns it.
113+
func (rc *Ramcache) Remove(key string) (interface{}, error) {
114+
rc.Lock()
115+
defer rc.Unlock()
116+
i, ok := rc.cache[key]
117+
if !ok {
118+
return nil, ErrNotFound
119+
}
120+
heap.Remove(&rc.tqueue, i.index)
121+
delete(rc.cache, key)
122+
return i.value, nil
123+
}
124+
125+
// CreatedAt returns the time the key was inserted into the cache.
126+
func (rc *Ramcache) CreatedAt(key string) (t time.Time, err error) {
127+
rc.RLock()
128+
defer rc.RUnlock()
129+
i, ok := rc.cache[key]
130+
if !ok {
131+
err = ErrNotFound
132+
return
133+
}
134+
t = i.createdAt
135+
return
136+
}
137+
138+
// Count returns the number of elements in the cache.
139+
func (rc *Ramcache) Count() int {
140+
rc.RLock()
141+
defer rc.RUnlock()
142+
return len(rc.cache)
143+
}
144+
145+
// Keys returns all the keys in the cache.
146+
func (rc *Ramcache) Keys() []string {
147+
rc.RLock()
148+
defer rc.RUnlock()
149+
var result []string
150+
for k := range rc.cache {
151+
result = append(result, k)
152+
}
153+
return result
154+
}
155+
156+
func (rc *Ramcache) cleanup() {
157+
for {
158+
time.Sleep(10 * time.Second)
159+
rc.clean(time.Now())
160+
}
161+
}
162+
163+
func (rc *Ramcache) clean(now time.Time) {
164+
rc.Lock()
165+
defer rc.Unlock()
166+
if rc.frozen {
167+
return
168+
}
169+
for i := 0; i < 10000; i++ {
170+
if rc.tqueue.Len() == 0 {
171+
return
172+
}
173+
top := heap.Pop(&rc.tqueue).(*item)
174+
if now.Sub(top.accessedAt) > rc.TTL {
175+
fmt.Printf("deleting item %s. Last access at: %s (%s ago).\n", top.key, top.accessedAt, now.Sub(top.accessedAt))
176+
delete(rc.cache, top.key)
177+
} else {
178+
heap.Push(&rc.tqueue, top)
179+
return
180+
}
181+
}
182+
}
183+
184+
// Freeze stops Ramcache from removing any expired entries.
185+
func (rc *Ramcache) Freeze() {
186+
rc.Lock()
187+
rc.frozen = true
188+
rc.Unlock()
189+
}
190+
191+
// Each will call f for every entry in the cache.
192+
func (rc *Ramcache) Each(f func(key string, value interface{})) {
193+
rc.RLock()
194+
defer rc.Unlock()
195+
for k, v := range rc.cache {
196+
f(k, v.value)
197+
}
198+
}
199+
200+
// An item is something cached in the Ramcache, and managed in the timeQueue.
201+
type item struct {
202+
key string
203+
value interface{}
204+
createdAt time.Time
205+
accessedAt time.Time
206+
index int
207+
}
208+
209+
func newItem(key string, val interface{}) *item {
210+
now := time.Now()
211+
return &item{key: key, value: val, createdAt: now, accessedAt: now, index: -1}
212+
}
213+
214+
// A timeQueue implements heap.Interface and holds items
215+
type timeQueue []*item
216+
217+
func (tq timeQueue) Len() int { return len(tq) }
218+
219+
func (tq timeQueue) Less(i, j int) bool {
220+
return tq[i].accessedAt.Before(tq[j].accessedAt)
221+
}
222+
223+
func (tq timeQueue) Swap(i, j int) {
224+
tq[i], tq[j] = tq[j], tq[i]
225+
tq[i].index = i
226+
tq[j].index = j
227+
}
228+
229+
func (tq *timeQueue) Push(x interface{}) {
230+
a := *tq
231+
n := len(a)
232+
// a = a[0 : n+1]
233+
itm := x.(*item)
234+
itm.index = n
235+
// a[n] = itm
236+
a = append(a, itm)
237+
*tq = a
238+
}
239+
240+
func (tq *timeQueue) Pop() interface{} {
241+
a := *tq
242+
n := len(a)
243+
itm := a[n-1]
244+
itm.index = -1
245+
*tq = a[0 : n-1]
246+
return itm
247+
}
248+
249+
func (tq *timeQueue) Access(itm *item) {
250+
heap.Remove(tq, itm.index)
251+
itm.accessedAt = time.Now()
252+
heap.Push(tq, itm)
253+
}
254+
255+
func (tq *timeQueue) Insert(itm *item) {
256+
heap.Push(tq, itm)
257+
}
258+
259+
// Bool is a convenience method to type assert a Ramcache reply
260+
// into a boolean value.
261+
func Bool(reply interface{}, err error) (bool, error) {
262+
if err != nil {
263+
return false, err
264+
}
265+
b, ok := reply.(bool)
266+
if !ok {
267+
return false, fmt.Errorf("ramcache: unexpected type for Bool, got %T", reply)
268+
}
269+
return b, nil
270+
}

0 commit comments

Comments
 (0)