2018-12-05 21:13:29 +08:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"runtime"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Cache store element with a expired time
|
2022-04-05 23:29:52 +08:00
|
|
|
type Cache[K comparable, V any] struct {
|
|
|
|
*cache[K, V]
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
|
|
|
|
2022-04-05 23:29:52 +08:00
|
|
|
type cache[K comparable, V any] struct {
|
2018-12-05 21:13:29 +08:00
|
|
|
mapping sync.Map
|
2022-04-05 23:29:52 +08:00
|
|
|
janitor *janitor[K, V]
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
|
|
|
|
2022-04-05 23:29:52 +08:00
|
|
|
type element[V any] struct {
|
2018-12-05 21:13:29 +08:00
|
|
|
Expired time.Time
|
2022-04-05 23:29:52 +08:00
|
|
|
Payload V
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Put element in Cache with its ttl
|
2022-04-05 23:29:52 +08:00
|
|
|
func (c *cache[K, V]) Put(key K, payload V, ttl time.Duration) {
|
|
|
|
c.mapping.Store(key, &element[V]{
|
2018-12-05 21:13:29 +08:00
|
|
|
Payload: payload,
|
|
|
|
Expired: time.Now().Add(ttl),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get element in Cache, and drop when it expired
|
2022-04-05 23:29:52 +08:00
|
|
|
func (c *cache[K, V]) Get(key K) V {
|
2018-12-05 21:13:29 +08:00
|
|
|
item, exist := c.mapping.Load(key)
|
|
|
|
if !exist {
|
2022-04-05 23:29:52 +08:00
|
|
|
return getZero[V]()
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
2022-04-05 23:29:52 +08:00
|
|
|
elm := item.(*element[V])
|
2018-12-05 21:13:29 +08:00
|
|
|
// expired
|
|
|
|
if time.Since(elm.Expired) > 0 {
|
|
|
|
c.mapping.Delete(key)
|
2022-04-05 23:29:52 +08:00
|
|
|
return getZero[V]()
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
|
|
|
return elm.Payload
|
|
|
|
}
|
|
|
|
|
2019-01-25 15:38:14 +08:00
|
|
|
// GetWithExpire element in Cache with Expire Time
|
2022-04-05 23:29:52 +08:00
|
|
|
func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
|
2019-01-25 15:38:14 +08:00
|
|
|
item, exist := c.mapping.Load(key)
|
|
|
|
if !exist {
|
|
|
|
return
|
|
|
|
}
|
2022-04-05 23:29:52 +08:00
|
|
|
elm := item.(*element[V])
|
2019-01-25 15:38:14 +08:00
|
|
|
// expired
|
|
|
|
if time.Since(elm.Expired) > 0 {
|
|
|
|
c.mapping.Delete(key)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return elm.Payload, elm.Expired
|
|
|
|
}
|
|
|
|
|
2022-04-05 23:29:52 +08:00
|
|
|
func (c *cache[K, V]) cleanup() {
|
2022-03-16 12:10:13 +08:00
|
|
|
c.mapping.Range(func(k, v any) bool {
|
2018-12-05 21:13:29 +08:00
|
|
|
key := k.(string)
|
2022-04-05 23:29:52 +08:00
|
|
|
elm := v.(*element[V])
|
2018-12-05 21:13:29 +08:00
|
|
|
if time.Since(elm.Expired) > 0 {
|
|
|
|
c.mapping.Delete(key)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-05 23:29:52 +08:00
|
|
|
type janitor[K comparable, V any] struct {
|
2018-12-05 21:13:29 +08:00
|
|
|
interval time.Duration
|
|
|
|
stop chan struct{}
|
|
|
|
}
|
|
|
|
|
2022-04-05 23:29:52 +08:00
|
|
|
func (j *janitor[K, V]) process(c *cache[K, V]) {
|
2018-12-05 21:13:29 +08:00
|
|
|
ticker := time.NewTicker(j.interval)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
c.cleanup()
|
|
|
|
case <-j.stop:
|
|
|
|
ticker.Stop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-05 23:29:52 +08:00
|
|
|
func stopJanitor[K comparable, V any](c *Cache[K, V]) {
|
2018-12-05 21:13:29 +08:00
|
|
|
c.janitor.stop <- struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// New return *Cache
|
2022-04-05 23:29:52 +08:00
|
|
|
func New[K comparable, V any](interval time.Duration) *Cache[K, V] {
|
|
|
|
j := &janitor[K, V]{
|
2018-12-05 21:13:29 +08:00
|
|
|
interval: interval,
|
|
|
|
stop: make(chan struct{}),
|
|
|
|
}
|
2022-04-05 23:29:52 +08:00
|
|
|
c := &cache[K, V]{janitor: j}
|
2018-12-05 21:13:29 +08:00
|
|
|
go j.process(c)
|
2022-04-05 23:29:52 +08:00
|
|
|
C := &Cache[K, V]{c}
|
|
|
|
runtime.SetFinalizer(C, stopJanitor[K, V])
|
2018-12-05 21:13:29 +08:00
|
|
|
return C
|
|
|
|
}
|