-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlru.go
124 lines (104 loc) · 2.69 KB
/
lru.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
// Copyright 2017 <CompanyName>, Inc. All Rights Reserved.
package hashgen
import (
"container/list"
"encoding/json"
"log"
"sync"
)
const defaultCapacity = 1000
type UserRecord struct {
// unique per hash POST request (job id)
Uuid string
// unique per user (when used)
Salt []byte
HashBytes []byte
}
// A LRU cache that is safe for concurrent access. All items are also added to
// persistent storage.
type LruCache struct {
sync.RWMutex
// Items are evicted when cache is full, can be fetched from persistent storage
Capacity int
// The Value of each list element will be a *UserRecord
linkedList *list.List
// The Value of each list element will be a *UserRecord
catalog map[string]*list.Element
// Persistent storage for crypto hashes by uuid
dao *UserAccountFile
}
func NewCache(capacity int, filename string) *LruCache {
useCapacity := defaultCapacity
if capacity > 0 {
useCapacity = capacity
}
return &LruCache{
Capacity: useCapacity,
linkedList: list.New(),
catalog: make(map[string]*list.Element, useCapacity), // size hint is capacity
dao: New(filename),
}
}
// Assumes record is not already in cache, which seems reasonable given that
// the key is uuid. Consider adding lookup for safety.
func (cache *LruCache) Add(uuid string, salt []byte, hashbytes []byte) {
if cache == nil {
return
}
if len(uuid) == 0 {
return
}
log.Printf("Adding crypto hash bytes to cache for job %q\n", uuid)
// Takes a write lock while mutating cache
cache.Lock()
defer cache.Unlock()
r := UserRecord{uuid, salt, hashbytes}
element := cache.linkedList.PushFront(&r)
cache.catalog[uuid] = element
// Persist to disk so that eviction does not cause data loss
// TODO: check result for errors
cache.dao.Append(r)
if cache.linkedList.Len() > cache.Capacity {
go cache.Evict()
}
}
// Purge the oldest entry
func (cache *LruCache) Evict() {
if cache == nil {
return
}
cache.Lock()
defer cache.Unlock()
element := cache.linkedList.Back()
if element != nil {
uuid := element.Value.(*UserRecord).Uuid
delete(cache.catalog, uuid)
cache.linkedList.Remove(element)
}
}
func (cache *LruCache) Get(uuid string) (value *UserRecord, ok bool) {
if cache.catalog == nil {
return
}
log.Printf("getting record with uuid: %s\n", uuid)
cache.RLock()
element, ok := cache.catalog[uuid]
cache.RUnlock()
if ok {
cache.Lock()
defer cache.Unlock()
cache.linkedList.MoveToFront(element)
if m, ok := element.Value.(*UserRecord); ok {
return m, true
}
} else {
// TODO: Seems expected that this should Add to cache
if b, ok := cache.dao.Get(uuid); ok {
var m UserRecord
if err := json.Unmarshal(b, &m); err == nil {
return &m, ok
}
}
}
return
}