github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/cache/asynccache/asynccache.go (about) 1 // Copyright 2021 ByteDance Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package asynccache 16 17 import ( 18 "fmt" 19 "log" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 sf "golang.org/x/sync/singleflight" 25 ) 26 27 // Options controls the behavior of AsyncCache. 28 type Options struct { 29 RefreshDuration time.Duration 30 Fetcher func(key string) (interface{}, error) 31 32 // If EnableExpire is true, ExpireDuration MUST be set. 33 EnableExpire bool 34 ExpireDuration time.Duration 35 36 ErrorHandler func(key string, err error) 37 ChangeHandler func(key string, oldData, newData interface{}) 38 DeleteHandler func(key string, oldData interface{}) 39 40 IsSame func(key string, oldData, newData interface{}) bool 41 ErrLogFunc func(str string) 42 } 43 44 // AsyncCache . 45 type AsyncCache interface { 46 // SetDefault sets the default value of given key if it is new to the cache. 47 // It is useful for cache warming up. 48 // Param val should not be nil. 49 SetDefault(key string, val interface{}) (exist bool) 50 51 // Get tries to fetch a value corresponding to the given key from the cache. 52 // If error occurs during the first time fetching, it will be cached until the 53 // sequential fetching triggered by the refresh goroutine succeed. 54 Get(key string) (val interface{}, err error) 55 56 // GetOrSet tries to fetch a value corresponding to the given key from the cache. 57 // If the key is not yet cached or error occurs, the default value will be set. 58 GetOrSet(key string, defaultVal interface{}) (val interface{}) 59 60 // Dump dumps all cache entries. 61 // This will not cause expire to refresh. 62 Dump() map[string]interface{} 63 64 // DeleteIf deletes cached entries that match the `shouldDelete` predicate. 65 DeleteIf(shouldDelete func(key string) bool) 66 67 // Close closes the async cache. 68 // This should be called when the cache is no longer needed, or may lead to resource leak. 69 Close() 70 } 71 72 // asyncCache . 73 type asyncCache struct { 74 sfg sf.Group 75 opt Options 76 data sync.Map 77 } 78 79 type tickerType int 80 81 const ( 82 refreshTicker tickerType = iota 83 expireTicker 84 ) 85 86 type sharedTicker struct { 87 sync.Mutex 88 started bool 89 stopChan chan bool 90 ticker *time.Ticker 91 caches map[*asyncCache]struct{} 92 } 93 94 var ( 95 // 共用 ticker 96 refreshTickerMap, expireTickerMap sync.Map 97 ) 98 99 type entry struct { 100 val atomic.Value 101 expire int32 // 0 means useful, 1 will expire 102 err Error 103 } 104 105 func (e *entry) Store(x interface{}, err error) { 106 if x != nil { 107 e.val.Store(x) 108 } else { 109 e.val = atomic.Value{} 110 } 111 e.err.Store(err) 112 } 113 114 func (e *entry) Touch() { 115 atomic.StoreInt32(&e.expire, 0) 116 } 117 118 // NewAsyncCache creates an AsyncCache. 119 func NewAsyncCache(opt Options) AsyncCache { 120 c := &asyncCache{ 121 sfg: sf.Group{}, 122 opt: opt, 123 } 124 if c.opt.ErrLogFunc == nil { 125 c.opt.ErrLogFunc = func(str string) { 126 log.Println(str) 127 } 128 } 129 if c.opt.EnableExpire { 130 if c.opt.ExpireDuration == 0 { 131 panic("asynccache: invalid ExpireDuration") 132 } 133 ti, _ := expireTickerMap.LoadOrStore(c.opt.ExpireDuration, 134 &sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)}) 135 et := ti.(*sharedTicker) 136 et.Lock() 137 et.caches[c] = struct{}{} 138 if !et.started { 139 et.started = true 140 et.ticker = time.NewTicker(c.opt.ExpireDuration) 141 go et.tick(et.ticker, expireTicker) 142 } 143 et.Unlock() 144 } 145 146 ti, _ := refreshTickerMap.LoadOrStore(c.opt.RefreshDuration, 147 &sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)}) 148 rt := ti.(*sharedTicker) 149 rt.Lock() 150 rt.caches[c] = struct{}{} 151 if !rt.started { 152 rt.started = true 153 rt.ticker = time.NewTicker(c.opt.RefreshDuration) 154 go rt.tick(rt.ticker, refreshTicker) 155 } 156 rt.Unlock() 157 return c 158 } 159 160 // SetDefault sets the default value of given key if it is new to the cache. 161 func (c *asyncCache) SetDefault(key string, val interface{}) bool { 162 ety := &entry{} 163 ety.Store(val, nil) 164 actual, exist := c.data.LoadOrStore(key, ety) 165 if exist { 166 actual.(*entry).Touch() 167 } 168 return exist 169 } 170 171 // Get tries to fetch a value corresponding to the given key from the cache. 172 // If error occurs during in the first time fetching, it will be cached until the 173 // sequential fetchings triggered by the refresh goroutine succeed. 174 func (c *asyncCache) Get(key string) (val interface{}, err error) { 175 var ok bool 176 val, ok = c.data.Load(key) 177 if ok { 178 e := val.(*entry) 179 e.Touch() 180 return e.val.Load(), e.err.Load() 181 } 182 183 val, err, _ = c.sfg.Do(key, func() (v interface{}, e error) { 184 v, e = c.opt.Fetcher(key) 185 ety := &entry{} 186 ety.Store(v, e) 187 c.data.Store(key, ety) 188 return 189 }) 190 return 191 } 192 193 // GetOrSet tries to fetch a value corresponding to the given key from the cache. 194 // If the key is not yet cached or fetching failed, the default value will be set. 195 func (c *asyncCache) GetOrSet(key string, def interface{}) (val interface{}) { 196 if v, ok := c.data.Load(key); ok { 197 e := v.(*entry) 198 if e.err.Load() != nil { 199 ety := &entry{} 200 ety.Store(def, nil) 201 c.data.Store(key, ety) 202 return def 203 } 204 e.Touch() 205 return e.val.Load() 206 } 207 208 val, _, _ = c.sfg.Do(key, func() (interface{}, error) { 209 v, e := c.opt.Fetcher(key) 210 if e != nil { 211 v = def 212 } 213 ety := &entry{} 214 ety.Store(v, nil) 215 c.data.Store(key, ety) 216 return v, nil 217 }) 218 return 219 } 220 221 // Dump dumps all cached entries. 222 func (c *asyncCache) Dump() map[string]interface{} { 223 data := make(map[string]interface{}) 224 c.data.Range(func(key, val interface{}) bool { 225 k, ok := key.(string) 226 if !ok { 227 c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k)) 228 c.data.Delete(key) 229 return true 230 } 231 data[k] = val.(*entry).val.Load() 232 return true 233 }) 234 return data 235 } 236 237 // DeleteIf deletes cached entries that match the `shouldDelete` predicate. 238 func (c *asyncCache) DeleteIf(shouldDelete func(key string) bool) { 239 c.data.Range(func(key, value interface{}) bool { 240 s := key.(string) 241 if shouldDelete(s) { 242 if c.opt.DeleteHandler != nil { 243 go c.opt.DeleteHandler(s, value) 244 } 245 c.data.Delete(key) 246 } 247 return true 248 }) 249 } 250 251 // Close stops the background goroutine. 252 func (c *asyncCache) Close() { 253 // close refresh ticker 254 ti, _ := refreshTickerMap.Load(c.opt.RefreshDuration) 255 rt := ti.(*sharedTicker) 256 rt.Lock() 257 delete(rt.caches, c) 258 if len(rt.caches) == 0 { 259 rt.stopChan <- true 260 rt.started = false 261 } 262 rt.Unlock() 263 264 if c.opt.EnableExpire { 265 // close expire ticker 266 ti, _ := expireTickerMap.Load(c.opt.ExpireDuration) 267 et := ti.(*sharedTicker) 268 et.Lock() 269 delete(et.caches, c) 270 if len(et.caches) == 0 { 271 et.stopChan <- true 272 et.started = false 273 } 274 et.Unlock() 275 } 276 } 277 278 // tick . 279 // pass ticker but not use t.ticker directly is to ignore race. 280 func (t *sharedTicker) tick(ticker *time.Ticker, tt tickerType) { 281 var wg sync.WaitGroup 282 defer ticker.Stop() 283 for { 284 select { 285 case <-ticker.C: 286 t.Lock() 287 for c := range t.caches { 288 wg.Add(1) 289 go func(c *asyncCache) { 290 defer wg.Done() 291 if tt == expireTicker { 292 c.expire() 293 } else { 294 c.refresh() 295 } 296 }(c) 297 } 298 wg.Wait() 299 t.Unlock() 300 case stop := <-t.stopChan: 301 if stop { 302 return 303 } 304 } 305 } 306 } 307 308 func (c *asyncCache) expire() { 309 c.data.Range(func(key, value interface{}) bool { 310 k, ok := key.(string) 311 if !ok { 312 c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k)) 313 c.data.Delete(key) 314 return true 315 } 316 e, ok := value.(*entry) 317 if !ok { 318 c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value)) 319 c.data.Delete(key) 320 return true 321 } 322 if !atomic.CompareAndSwapInt32(&e.expire, 0, 1) { 323 if c.opt.DeleteHandler != nil { 324 go c.opt.DeleteHandler(k, value) 325 } 326 c.data.Delete(key) 327 } 328 329 return true 330 }) 331 } 332 333 func (c *asyncCache) refresh() { 334 c.data.Range(func(key, value interface{}) bool { 335 k, ok := key.(string) 336 if !ok { 337 c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k)) 338 c.data.Delete(key) 339 return true 340 } 341 e, ok := value.(*entry) 342 if !ok { 343 c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value)) 344 c.data.Delete(key) 345 return true 346 } 347 348 newVal, err := c.opt.Fetcher(k) 349 if err != nil { 350 if c.opt.ErrorHandler != nil { 351 go c.opt.ErrorHandler(k, err) 352 } 353 if e.err.Load() != nil { 354 e.err.Store(err) 355 } 356 return true 357 } 358 359 if c.opt.IsSame != nil && !c.opt.IsSame(k, e.val.Load(), newVal) { 360 if c.opt.ChangeHandler != nil { 361 go c.opt.ChangeHandler(k, e.val.Load(), newVal) 362 } 363 } 364 365 e.Store(newVal, err) 366 return true 367 }) 368 }