-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgottlmap.go
151 lines (137 loc) · 3.56 KB
/
gottlmap.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
package gottlmap
import (
"context"
"fmt"
"log"
"sync"
"time"
)
var (
// ErrKeyNotFound is returned when the key is not found
ErrTickerNotSet = fmt.Errorf("cleanupTicker not set")
ErrCtxNotSet = fmt.Errorf("ctx not set")
)
type Hook func(string, Element) error
// TTLMap is a container of map[string]*Element but with expirable Items.
type TTLMap struct {
data map[string]*Element
mu *sync.RWMutex
// cleanupTicker signals how often the expired elements are cleaned up
cleanupTicker *time.Ticker
cleanupPreHook Hook
ctx context.Context
}
// Element is a value that expires after a given Time
type Element struct {
Value interface{}
Ex time.Time
}
// New return a new TTLMap with the given cleanupTicker and cleanupPreHook
func New(t *time.Ticker, f Hook, ctx context.Context) (*TTLMap, error) {
if t == nil {
return nil, ErrTickerNotSet
}
if ctx == nil {
return nil, ErrCtxNotSet
}
tm := &TTLMap{
data: make(map[string]*Element),
mu: &sync.RWMutex{},
cleanupTicker: t,
//cleanupPreHook should only work on copy of the data map
cleanupPreHook: f,
ctx: ctx,
}
tm.startCleanupRoutine()
return tm, nil
}
// Set the value for the given key. If the key already exists, the value
// will be overwritten. If the key does not exist, it will be created.
// The key will expire after the given ttl.
// The duration must be greater than 0.
// ttl is in seconds.
func (m *TTLMap) Set(key string, value interface{}, ttl time.Duration) {
m.mu.Lock()
defer m.mu.Unlock()
ex := time.Now().Add(ttl)
m.data[key] = &Element{value, ex}
}
// Get returns the value for the given key. If the key does not exist,
// an empty Element is returned along with a false boolean.
func (m *TTLMap) Get(key string) (Element, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
a := Element{}
if a, ok := m.data[key]; ok {
return *a, true
}
return a, false
}
// Keys will return all keys of the TTLMap.
// The keys are returned are a snapshot of the keys at the time of the call.
// If the keys are modified after the call, the returned keys will not be updated.
func (m *TTLMap) Keys() []string {
m.mu.RLock()
defer m.mu.RUnlock()
keys := make([]string, 0, len(m.data))
for k := range m.data {
keys = append(keys, k)
}
return keys
}
// Values will return all Values of the TTLMap
// The values are returned are a snapshot of the values at the time of the call.
func (m *TTLMap) Values() []Element {
m.mu.RLock()
defer m.mu.RUnlock()
values := make([]Element, 0, len(m.data))
for _, v := range m.data {
values = append(values, *v)
}
return values
}
// GetDataCopy returns a copy of the data map
func (m *TTLMap) GetDataCopy() map[string]Element {
m.mu.Lock()
defer m.mu.Unlock()
x := make(map[string]Element)
for k, v := range m.data {
x[k] = *v
}
return x
}
// startCleanupRoutine starts the cleanup routine
func (m *TTLMap) startCleanupRoutine() {
fmt.Println("Starting cleanup routine")
go func() {
for {
select {
case <-m.cleanupTicker.C:
fmt.Println("Cleanup routine ticked")
m.cleanup()
case <-m.ctx.Done():
fmt.Println("Cleanup routine stopped")
return
}
}
}()
}
// cleanup removes all expired Elements from the map
// and invokes the cleanupPreHook if it is set
func (m *TTLMap) cleanup() {
// Below will prevent looking at entries in the current minute
now := time.Now()
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if v.Ex.Before(now) {
if m.cleanupPreHook != nil {
err := m.cleanupPreHook(k, *v)
if err != nil {
log.Println(err)
}
}
delete(m.data, k)
}
}
}