-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathttlmap.go
145 lines (131 loc) · 3.77 KB
/
ttlmap.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
//
// Copyright (c) Dmitri Toubelis
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ttlmap
import (
"context"
"math/rand"
"sync"
"time"
)
type item struct {
cancel func()
val interface{}
}
// TTLMap is obscure context
type TTLMap struct {
sync.RWMutex
m map[string]*item
ttl time.Duration
}
// New creates a new TTLMap with a specified default TTL
func New(ttl time.Duration) *TTLMap {
if ttl <= 0 {
panic("invalid TTL")
}
x := TTLMap{
m: map[string]*item{},
ttl: ttl,
}
return &x
}
func (t *TTLMap) delayedRemove(ctx context.Context, key string, itemRef *item, ttl time.Duration) {
// calculate random delay within 0-100ms range to reduce probability of collisions
dly := time.Microsecond * time.Duration(rand.Intn(100*1000))
// wait for an event
select {
case <-ctx.Done():
break
case <-time.After(ttl + dly):
break
}
t.Lock()
defer t.Unlock()
// check for itemRef before deletion to work around a possible race condition
if v, ok := t.m[key]; ok && v == itemRef {
delete(t.m, key)
}
}
func (t *TTLMap) insert(ctx context.Context, key string, val interface{}, ttl time.Duration) {
// insert a new item
nctx, cancel := context.WithCancel(ctx)
i := &item{
cancel: cancel,
val: val,
}
t.m[key] = i
go t.delayedRemove(nctx, key, i, ttl)
}
// Put inserts a new value with a specified key into the map or replaces an existing one.
// It uses the default TTL specified during the initialization.
func (t *TTLMap) Put(ctx context.Context, key string, val interface{}) {
t.PutWithTTL(ctx, key, val, t.ttl)
}
// PutWithTTL inserts a new value with a specified key and TTL into the map or replaces an existing one.
func (t *TTLMap) PutWithTTL(ctx context.Context, key string, val interface{}, ttl time.Duration) {
if ttl <= 0 {
panic("ttl is expected to be >0")
}
t.Lock()
defer t.Unlock()
// cancel the context of the existing value
if v := t.m[key]; v != nil {
v.cancel()
}
// insert a new value
t.insert(ctx, key, val, ttl)
}
// TestAndPut inserts a new value with a specified key into the map only if none exists.
// Otherwise, it does nothing and returns `false`. It uses the default TTL specified during the initialization.
func (t *TTLMap) TestAndPut(ctx context.Context, key string, val interface{}) bool {
return t.TestAndPutWithTTL(ctx, key, val, t.ttl)
}
// TestAndPutWithTTL inserts a new value with a specified key and TTL into the map only if none exists.
// Otherwise, it does nothing and returns `false`.
func (t *TTLMap) TestAndPutWithTTL(ctx context.Context, key string, val interface{}, ttl time.Duration) bool {
t.Lock()
defer t.Unlock()
// check for an existing value
if _, ok := t.m[key]; ok {
return false
}
// insert a new value
t.insert(ctx, key, val, t.ttl)
return true
}
// Get returns a value from the map with a specified key or nil if one not present.
func (t *TTLMap) Get(key string) (interface{}, bool) {
t.RLock()
defer t.RUnlock()
x, ok := t.m[key]
if ok {
return x.val, ok
}
return nil, false
}
// Len returns number of items in the map
func (t *TTLMap) Len() int {
t.RLock()
defer t.RUnlock()
return len(t.m)
}
// Clear cancels any pending operations and clears the content of the map
func (t *TTLMap) Clear() {
t.Lock()
defer t.Unlock()
for _, v := range t.m {
v.cancel()
}
t.m = map[string]*item{}
}