forked from imkira/go-ttlmap
/
map.go
178 lines (162 loc) · 4.03 KB
/
map.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Package ttlmap provides a map-like interface with string keys and expirable
// items. Keys are currently limited to strings.
package ttlmap
import "errors"
// Errors returned Map operations.
var (
ErrNotExist = errors.New("key does not exist")
ErrExist = errors.New("key already exists")
ErrDrained = errors.New("map was drained")
)
var zeroItem Item
// Map is the equivalent of a map[string]interface{} but with expirable Items.
type Map struct {
store *store
keeper *keeper
}
// New creates a new Map with given options.
func New(opts *Options) *Map {
if opts == nil {
opts = &Options{}
}
store := newStore(opts)
m := &Map{
store: store,
keeper: newKeeper(store),
}
go m.keeper.run()
return m
}
// Len returns the number of elements in the map.
func (m *Map) Len() int {
m.store.RLock()
n := len(m.store.kv)
m.store.RUnlock()
return n
}
// Get returns the item in the map with the given key.
// ErrNotExist will be returned if the key does not exist.
// ErrDrained will be returned if the map is already drained.
func (m *Map) Get(key string) (Item, error) {
m.store.RLock()
if m.keeper.drained {
m.store.RUnlock()
return zeroItem, ErrDrained
}
if pqi := m.store.kv[key]; pqi != nil {
item := *pqi.item
m.store.RUnlock()
return item, nil
}
m.store.RUnlock()
return zeroItem, ErrNotExist
}
// Set assigns an item with the specified key in the map.
// ErrExist or ErrNotExist may be returned depending on opts.KeyExist.
// ErrDrained will be returned if the map is already drained.
func (m *Map) Set(key string, item Item, opts *SetOptions) error {
m.store.Lock()
if m.keeper.drained {
m.store.Unlock()
return ErrDrained
}
err := m.set(key, &item, opts)
m.store.Unlock()
return err
}
// Update updates an item with the specified key in the map and returns it.
// ErrNotExist will be returned if the key does not exist.
// ErrDrained will be returned if the map is already drained.
func (m *Map) Update(key string, item Item, opts *UpdateOptions) (Item, error) {
m.store.Lock()
if m.keeper.drained {
m.store.Unlock()
return zeroItem, ErrDrained
}
if pqi := m.store.kv[key]; pqi != nil {
m.update(pqi, &item, opts)
item = *pqi.item
m.store.Unlock()
return item, nil
}
m.store.Unlock()
return zeroItem, ErrNotExist
}
// Delete deletes the item with the specified key from the map.
// ErrNotExist will be returned if the key does not exist.
// ErrDrained will be returned if the map is already drained.
func (m *Map) Delete(key string) (Item, error) {
m.store.Lock()
if m.keeper.drained {
m.store.Unlock()
return zeroItem, ErrDrained
}
if pqi := m.store.kv[key]; pqi != nil {
m.delete(pqi)
item := *pqi.item
m.store.Unlock()
return item, nil
}
m.store.Unlock()
return zeroItem, ErrNotExist
}
// Draining returns the channel that is closed when the map starts draining.
func (m *Map) Draining() <-chan struct{} {
return m.keeper.drainingChan
}
// Drain evicts all remaining elements from the map and terminates the usage of
// this map.
func (m *Map) Drain() {
m.keeper.signalDrain()
<-m.keeper.doneChan
}
func (m *Map) set(key string, item *Item, opts *SetOptions) error {
if pqi := m.store.kv[key]; pqi != nil {
if opts.keyExist() == KeyExistNotYet {
return ErrExist
}
m.expireOrEvict(pqi)
} else if opts.keyExist() == KeyExistAlready {
return ErrNotExist
}
pqi := &pqitem{
key: key,
item: item,
index: -1,
}
m.store.set(pqi)
if pqi.index == 0 {
m.keeper.signalUpdate()
}
return nil
}
func (m *Map) update(pqi *pqitem, item *Item, opts *UpdateOptions) {
if opts != nil {
if opts.KeepValue {
item.value = pqi.item.value
}
if opts.KeepExpiration {
item.expiration = pqi.item.expiration
item.expires = pqi.item.expires
}
}
pqi.item = item
m.store.fix(pqi)
if pqi.index == 0 {
m.keeper.signalUpdate()
}
}
func (m *Map) expireOrEvict(pqi *pqitem) {
if pqi.index == 0 {
m.keeper.signalUpdate()
}
if !m.store.tryExpire(pqi) {
m.store.evict(pqi)
}
}
func (m *Map) delete(pqi *pqitem) {
if pqi.index == 0 {
m.keeper.signalUpdate()
}
m.store.delete(pqi)
}