-
Notifications
You must be signed in to change notification settings - Fork 0
/
inmemory.go
160 lines (124 loc) · 3.58 KB
/
inmemory.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
159
160
package layer
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
"time"
)
//The InMemoryCacheLayer stores responses in memory
// This is a simple example without advanced features
type InMemoryCacheLayer struct {
//Maximum size of the cache in bytes
MaxSize int
entityStore map[string]inMemoryCacheEntity
entityStoreMutex sync.RWMutex
currentSize int
staleKeys map[string]bool
staleKeysMutex sync.Mutex
}
type inMemoryCacheEntity struct {
Data []byte
Expiration time.Time
}
func NewInMemoryCacheLayer(maxSize int) *InMemoryCacheLayer {
return &InMemoryCacheLayer{
MaxSize: maxSize,
entityStore: make(map[string]inMemoryCacheEntity, 500),
staleKeys: make(map[string]bool, 100),
}
}
func (layer *InMemoryCacheLayer) Get(key string) (io.ReadCloser, time.Duration, error) {
layer.entityStoreMutex.RLock()
defer layer.entityStoreMutex.RUnlock()
if entity, found := layer.entityStore[key]; found {
ttl := time.Until(entity.Expiration)
//If entry is stale
if ttl <= 0 {
layer.staleKeysMutex.Lock()
layer.staleKeys[key] = true
layer.staleKeysMutex.Unlock()
}
return ioutil.NopCloser(bytes.NewReader(entity.Data)), ttl, nil
}
return nil, 0, nil
}
func (layer *InMemoryCacheLayer) Set(key string, entry io.ReadCloser, ttl time.Duration) error {
entryBytes, err := ioutil.ReadAll(entry)
defer entry.Close()
if err != nil {
return err
}
layer.entityStoreMutex.Lock()
defer layer.entityStoreMutex.Unlock()
availableRoom := (layer.MaxSize - layer.currentSize)
//If the entry is bigger than the max size we have to make room
if len(entryBytes) > availableRoom {
err := layer.replaceCache(availableRoom - len(entryBytes))
if err != nil {
return err
}
}
return layer.set(key, inMemoryCacheEntity{
Data: entryBytes,
Expiration: time.Now().Add(ttl),
})
}
func (layer *InMemoryCacheLayer) Delete(key string) error {
layer.entityStoreMutex.Lock()
layer.delete(key)
layer.entityStoreMutex.Unlock()
return nil
}
func (layer *InMemoryCacheLayer) Refresh(key string, ttl time.Duration) error {
layer.entityStoreMutex.Lock()
defer layer.entityStoreMutex.Unlock()
if entity, found := layer.entityStore[key]; found {
entity.Expiration = time.Now().Add(ttl)
layer.entityStore[key] = entity
return nil
}
return fmt.Errorf("Entity with key '%s' doesn't exist", key)
}
//WARNING call this function only when the layer is already write locked
func (layer *InMemoryCacheLayer) replaceCache(neededSize int) error {
//Loop over all known stale keys and remove them until we have room or there are no more stale keys
layer.staleKeysMutex.Lock()
for key := range layer.staleKeys {
neededSize -= layer.delete(key)
delete(layer.staleKeys, key)
//If we have enough space we return
if neededSize <= 0 {
layer.staleKeysMutex.Unlock()
return nil
}
}
layer.staleKeysMutex.Unlock()
//If we still need room and there are no stale keys start removing fresh entries
for key := range layer.entityStore {
neededSize -= layer.delete(key)
//If we have enough space we return
if neededSize <= 0 {
return nil
}
}
return errors.New("Can't make enough room")
}
func (layer *InMemoryCacheLayer) delete(key string) int {
if entry, found := layer.entityStore[key]; found {
size := len(entry.Data)
delete(layer.entityStore, key)
layer.currentSize -= size
return size
}
return 0
}
func (layer *InMemoryCacheLayer) set(key string, entry inMemoryCacheEntity) error {
//Delete the key first so the current size is updated
layer.delete(key)
layer.currentSize += len(entry.Data)
layer.entityStore[key] = entry
return nil
}