github.com/mailgun/holster/v4@v4.20.0/collections/ttlmap.go (about) 1 /* 2 Copyright 2017 Mailgun Technologies Inc 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 package collections 17 18 import ( 19 "fmt" 20 "sync" 21 "time" 22 23 "github.com/mailgun/holster/v4/clock" 24 ) 25 26 type TTLMap struct { 27 // Optionally specifies a callback function to be 28 // executed when an entry has expired 29 OnExpire func(key string, i interface{}) 30 31 capacity int 32 elements map[string]*mapElement 33 expiryTimes *PriorityQueue 34 mutex *sync.RWMutex 35 } 36 37 type mapElement struct { 38 key string 39 value interface{} 40 heapEl *PQItem 41 } 42 43 func NewTTLMap(capacity int) *TTLMap { 44 if capacity <= 0 { 45 capacity = 0 46 } 47 48 return &TTLMap{ 49 capacity: capacity, 50 elements: make(map[string]*mapElement), 51 expiryTimes: NewPriorityQueue(), 52 mutex: &sync.RWMutex{}, 53 } 54 } 55 56 func (m *TTLMap) Set(key string, value interface{}, ttlSeconds int) error { 57 expiryTime, err := m.toEpochSeconds(ttlSeconds) 58 if err != nil { 59 return err 60 } 61 m.mutex.Lock() 62 defer m.mutex.Unlock() 63 m.set(key, value, expiryTime) 64 return nil 65 } 66 67 func (m *TTLMap) Len() int { 68 m.mutex.RLock() 69 defer m.mutex.RUnlock() 70 return len(m.elements) 71 } 72 73 func (m *TTLMap) Get(key string) (interface{}, bool) { 74 value, mapEl, expired := m.lockNGet(key) 75 if mapEl == nil { 76 return nil, false 77 } 78 if expired { 79 m.lockNDel(mapEl) 80 return nil, false 81 } 82 return value, true 83 } 84 85 func (m *TTLMap) Increment(key string, value, ttlSeconds int) (retval int, reterr error) { 86 expiryTime, err := m.toEpochSeconds(ttlSeconds) 87 if err != nil { 88 return 0, err 89 } 90 91 m.mutex.Lock() 92 defer m.mutex.Unlock() 93 94 mapEl, expired := m.get(key) 95 if mapEl == nil || expired { 96 m.set(key, value, expiryTime) 97 return value, nil 98 } 99 100 currentValue, ok := mapEl.value.(int) 101 if !ok { 102 return 0, fmt.Errorf("Expected existing value to be integer, got %T", mapEl.value) //nolint:stylecheck // TODO(v5): ST1005: error strings should not be capitalized 103 } 104 105 currentValue += value 106 m.set(key, currentValue, expiryTime) 107 return currentValue, nil 108 } 109 110 func (m *TTLMap) GetInt(key string) (retval int, found bool, reterr error) { 111 valueI, exists := m.Get(key) 112 if !exists { 113 return 0, false, nil 114 } 115 value, ok := valueI.(int) 116 if !ok { 117 return 0, false, fmt.Errorf("Expected existing value to be integer, got %T", valueI) //nolint:stylecheck // TODO(v5): ST1005: error strings should not be capitalized 118 } 119 return value, true, nil 120 } 121 122 func (m *TTLMap) set(key string, value interface{}, expiryTime int) { 123 if mapEl, ok := m.elements[key]; ok { 124 mapEl.value = value 125 m.expiryTimes.Update(mapEl.heapEl, expiryTime) 126 return 127 } 128 129 if len(m.elements) >= m.capacity { 130 m.freeSpace(1) 131 } 132 heapEl := &PQItem{ 133 Priority: expiryTime, 134 } 135 mapEl := &mapElement{ 136 key: key, 137 value: value, 138 heapEl: heapEl, 139 } 140 heapEl.Value = mapEl 141 m.elements[key] = mapEl 142 m.expiryTimes.Push(heapEl) 143 } 144 145 func (m *TTLMap) lockNGet(key string) (value interface{}, mapEl *mapElement, expired bool) { 146 m.mutex.RLock() 147 defer m.mutex.RUnlock() 148 149 mapEl, expired = m.get(key) 150 value = nil 151 if mapEl != nil { 152 value = mapEl.value 153 } 154 return value, mapEl, expired 155 } 156 157 func (m *TTLMap) get(key string) (*mapElement, bool) { 158 mapEl, ok := m.elements[key] 159 if !ok { 160 return nil, false 161 } 162 now := int(clock.Now().Unix()) 163 expired := mapEl.heapEl.Priority <= now 164 return mapEl, expired 165 } 166 167 func (m *TTLMap) lockNDel(mapEl *mapElement) { 168 m.mutex.Lock() 169 defer m.mutex.Unlock() 170 171 // Map element could have been updated. Now that we have a lock 172 // retrieve it again and check if it is still expired. 173 var ok bool 174 if mapEl, ok = m.elements[mapEl.key]; !ok { 175 return 176 } 177 now := int(clock.Now().Unix()) 178 if mapEl.heapEl.Priority > now { 179 return 180 } 181 182 if m.OnExpire != nil { 183 m.OnExpire(mapEl.key, mapEl.value) 184 } 185 186 delete(m.elements, mapEl.key) 187 m.expiryTimes.Remove(mapEl.heapEl) 188 } 189 190 func (m *TTLMap) freeSpace(count int) { 191 removed := m.RemoveExpired(count) 192 if removed >= count { 193 return 194 } 195 m.RemoveLastUsed(count - removed) 196 } 197 198 func (m *TTLMap) RemoveExpired(iterations int) int { 199 removed := 0 200 now := int(clock.Now().Unix()) 201 for i := 0; i < iterations; i += 1 { 202 if len(m.elements) == 0 { 203 break 204 } 205 heapEl := m.expiryTimes.Peek() 206 if heapEl.Priority > now { 207 break 208 } 209 m.expiryTimes.Pop() 210 mapEl := heapEl.Value.(*mapElement) 211 delete(m.elements, mapEl.key) 212 removed += 1 213 } 214 return removed 215 } 216 217 func (m *TTLMap) RemoveLastUsed(iterations int) { 218 for i := 0; i < iterations; i += 1 { 219 if len(m.elements) == 0 { 220 return 221 } 222 heapEl := m.expiryTimes.Pop() 223 mapEl := heapEl.Value.(*mapElement) 224 delete(m.elements, mapEl.key) 225 } 226 } 227 228 func (m *TTLMap) toEpochSeconds(ttlSeconds int) (int, error) { 229 if ttlSeconds <= 0 { 230 return 0, fmt.Errorf("ttlSeconds should be >= 0, got %d", ttlSeconds) 231 } 232 return int(clock.Now().Add(time.Second * time.Duration(ttlSeconds)).Unix()), nil 233 }