pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/cache/cache.go (about) 1 // Package cache provides a simple in-memory key:value cache 2 package cache 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "sync" 13 "time" 14 ) 15 16 // ////////////////////////////////////////////////////////////////////////////////// // 17 18 // Cache is cache instance 19 type Cache struct { 20 expiration time.Duration 21 data map[string]interface{} 22 expiry map[string]int64 23 mu *sync.RWMutex 24 isJanitorWorks bool 25 } 26 27 // ////////////////////////////////////////////////////////////////////////////////// // 28 29 // New creates new cache instance 30 func New(defaultExpiration, cleanupInterval time.Duration) *Cache { 31 s := &Cache{ 32 expiration: defaultExpiration, 33 data: make(map[string]interface{}), 34 expiry: make(map[string]int64), 35 mu: &sync.RWMutex{}, 36 } 37 38 if cleanupInterval != 0 { 39 s.isJanitorWorks = true 40 go s.janitor(cleanupInterval) 41 } 42 43 return s 44 } 45 46 // ////////////////////////////////////////////////////////////////////////////////// // 47 48 // Has returns true if cache contains data for given key 49 func (s *Cache) Has(key string) bool { 50 if s == nil { 51 return false 52 } 53 54 s.mu.RLock() 55 56 expiration, ok := s.expiry[key] 57 58 if !ok { 59 s.mu.RUnlock() 60 return false 61 } 62 63 if time.Now().UnixNano() > expiration { 64 s.mu.RUnlock() 65 66 if !s.isJanitorWorks { 67 s.Delete(key) 68 } 69 70 return false 71 } 72 73 s.mu.RUnlock() 74 75 return ok 76 } 77 78 // Size returns number of items in cache 79 func (s *Cache) Size() int { 80 if s == nil { 81 return 0 82 } 83 84 s.mu.RLock() 85 defer s.mu.RUnlock() 86 87 return len(s.data) 88 } 89 90 // Expired returns number of exipred items in cache 91 func (s *Cache) Expired() int { 92 if s == nil { 93 return 0 94 } 95 96 items := 0 97 now := time.Now().UnixNano() 98 99 s.mu.Lock() 100 101 for _, expiration := range s.expiry { 102 if now > expiration { 103 items++ 104 } 105 } 106 107 s.mu.Unlock() 108 109 return items 110 } 111 112 // Set adds or updates item in cache 113 func (s *Cache) Set(key string, data interface{}) { 114 if s == nil { 115 return 116 } 117 118 s.mu.Lock() 119 120 s.expiry[key] = time.Now().Add(s.expiration).UnixNano() 121 s.data[key] = data 122 123 s.mu.Unlock() 124 } 125 126 // Get returns item from cache or nil 127 func (s *Cache) Get(key string) interface{} { 128 if s == nil { 129 return nil 130 } 131 132 s.mu.RLock() 133 134 expiration, ok := s.expiry[key] 135 136 if !ok { 137 s.mu.RUnlock() 138 return nil 139 } 140 141 if time.Now().UnixNano() > expiration { 142 s.mu.RUnlock() 143 144 if !s.isJanitorWorks { 145 s.Delete(key) 146 } 147 148 return nil 149 } 150 151 item := s.data[key] 152 153 s.mu.RUnlock() 154 155 return item 156 } 157 158 // GetWithExpiration returns item from cache and expiration date or nil 159 func (s *Cache) GetWithExpiration(key string) (interface{}, time.Time) { 160 if s == nil { 161 return nil, time.Time{} 162 } 163 164 s.mu.RLock() 165 166 expiration, ok := s.expiry[key] 167 168 if !ok { 169 s.mu.RUnlock() 170 return nil, time.Time{} 171 } 172 173 if time.Now().UnixNano() > expiration { 174 s.mu.RUnlock() 175 176 if !s.isJanitorWorks { 177 s.Delete(key) 178 } 179 180 return nil, time.Time{} 181 } 182 183 item := s.data[key] 184 185 s.mu.RUnlock() 186 187 return item, time.Unix(0, expiration) 188 } 189 190 // Delete removes item from cache 191 func (s *Cache) Delete(key string) { 192 if s == nil { 193 return 194 } 195 196 s.mu.Lock() 197 198 delete(s.data, key) 199 delete(s.expiry, key) 200 201 s.mu.Unlock() 202 } 203 204 // Flush removes all data from cache 205 func (s *Cache) Flush() { 206 if s == nil { 207 return 208 } 209 210 s.mu.Lock() 211 212 s.data = make(map[string]interface{}) 213 s.expiry = make(map[string]int64) 214 215 s.mu.Unlock() 216 } 217 218 // ////////////////////////////////////////////////////////////////////////////////// // 219 220 func (s *Cache) janitor(interval time.Duration) { 221 for range time.NewTicker(interval).C { 222 if len(s.data) == 0 { 223 continue 224 } 225 226 now := time.Now().UnixNano() 227 228 s.mu.Lock() 229 230 for key, expiration := range s.expiry { 231 if now > expiration { 232 delete(s.data, key) 233 delete(s.expiry, key) 234 } 235 } 236 237 s.mu.Unlock() 238 } 239 }