158 lines
3.5 KiB
Go
158 lines
3.5 KiB
Go
|
package dsUtils
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type KeyValue struct {
|
||
|
Key string
|
||
|
Value []byte
|
||
|
}
|
||
|
|
||
|
type MaybeExpirableData struct {
|
||
|
Data []byte
|
||
|
ExpirationTime time.Time
|
||
|
CanExpire bool
|
||
|
}
|
||
|
type ToBeInvalidatedData struct {
|
||
|
Key string
|
||
|
ExpirationTime time.Time
|
||
|
}
|
||
|
|
||
|
type ICache interface {
|
||
|
Get(key string) (item []byte, isSet bool)
|
||
|
Set(data map[string][]byte, canExpire bool, updateExpirationTime bool)
|
||
|
Remove(key string)
|
||
|
SetIfNotExists(key string, value []byte, canExpire bool) (isSet bool)
|
||
|
GetAndRemove(key string) (item []byte, isSet bool)
|
||
|
|
||
|
Run(ctx context.Context) error
|
||
|
}
|
||
|
|
||
|
// Cache provides simple cache functionality
|
||
|
// All objects in the same cache instance are getting same TTL
|
||
|
type Cache struct {
|
||
|
ICache
|
||
|
newExpirableItem *sync.Cond
|
||
|
lock *sync.Mutex
|
||
|
data map[string]MaybeExpirableData
|
||
|
invalidateOrder []ToBeInvalidatedData
|
||
|
ttl time.Duration
|
||
|
}
|
||
|
|
||
|
// NewCache Creates new Cache instance
|
||
|
func NewCache(ttl time.Duration) *Cache {
|
||
|
return &Cache{
|
||
|
ttl: ttl,
|
||
|
newExpirableItem: &sync.Cond{L: &sync.Mutex{}},
|
||
|
lock: &sync.Mutex{},
|
||
|
data: make(map[string]MaybeExpirableData),
|
||
|
invalidateOrder: make([]ToBeInvalidatedData, 0),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Cache) get(key string) (item MaybeExpirableData, isSet bool) {
|
||
|
item, isSet = c.data[key]
|
||
|
if item.ExpirationTime.Before(time.Now()) {
|
||
|
delete(c.data, key)
|
||
|
return MaybeExpirableData{}, false
|
||
|
}
|
||
|
return item, isSet
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Get(key string) (data []byte, isSet bool) {
|
||
|
c.lock.Lock()
|
||
|
defer c.lock.Unlock()
|
||
|
item, isSet := c.get(key)
|
||
|
return item.Data, isSet
|
||
|
}
|
||
|
|
||
|
func (c *Cache) GetAndRemove(key string) (data []byte, isSet bool) {
|
||
|
c.lock.Lock()
|
||
|
item, isSet := c.get(key)
|
||
|
delete(c.data, key)
|
||
|
c.lock.Unlock()
|
||
|
return item.Data, isSet
|
||
|
}
|
||
|
|
||
|
// Internal function
|
||
|
func (c *Cache) set(key string, value []byte, canExpire bool, expirationTime time.Time) {
|
||
|
c.data[key] = MaybeExpirableData{
|
||
|
Data: value,
|
||
|
ExpirationTime: expirationTime,
|
||
|
CanExpire: canExpire,
|
||
|
}
|
||
|
if canExpire {
|
||
|
c.invalidateOrder = append(c.invalidateOrder, ToBeInvalidatedData{
|
||
|
Key: key,
|
||
|
ExpirationTime: expirationTime,
|
||
|
})
|
||
|
c.newExpirableItem.Signal()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Set(data map[string][]byte, canExpire bool, updateExpirationTime bool) {
|
||
|
c.lock.Lock()
|
||
|
for key, val := range data {
|
||
|
if !updateExpirationTime {
|
||
|
if item, exists := c.data[key]; exists {
|
||
|
c.set(key, val, item.CanExpire, item.ExpirationTime)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
c.set(key, val, canExpire, time.Now().Add(c.ttl))
|
||
|
}
|
||
|
c.lock.Unlock()
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Remove(key string) {
|
||
|
c.lock.Lock()
|
||
|
delete(c.data, key)
|
||
|
c.lock.Unlock()
|
||
|
}
|
||
|
|
||
|
func (c *Cache) SetIfNotExists(key string, value []byte, canExpire bool) (isSet bool) {
|
||
|
c.lock.Lock()
|
||
|
defer c.lock.Unlock()
|
||
|
_, exists := c.data[key]
|
||
|
if exists {
|
||
|
return false
|
||
|
}
|
||
|
c.set(key, value, canExpire, time.Now().Add(c.ttl))
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (c *Cache) timeInvalidate() {
|
||
|
c.lock.Lock()
|
||
|
if len(c.invalidateOrder) == 0 {
|
||
|
c.lock.Unlock()
|
||
|
c.newExpirableItem.Wait()
|
||
|
c.lock.Lock()
|
||
|
}
|
||
|
toBeInvalidated := c.invalidateOrder[0]
|
||
|
c.lock.Unlock()
|
||
|
time.After(time.Now().Sub(toBeInvalidated.ExpirationTime))
|
||
|
c.lock.Lock()
|
||
|
item, exists := c.data[toBeInvalidated.Key]
|
||
|
if exists && item.CanExpire {
|
||
|
if item.ExpirationTime.After(time.Now()) {
|
||
|
delete(c.data, toBeInvalidated.Key)
|
||
|
}
|
||
|
}
|
||
|
c.invalidateOrder = c.invalidateOrder[1:]
|
||
|
c.lock.Unlock()
|
||
|
}
|
||
|
|
||
|
func (c *Cache) Run(ctx context.Context) error {
|
||
|
// Invalidator loop
|
||
|
go func() {
|
||
|
for {
|
||
|
c.timeInvalidate()
|
||
|
}
|
||
|
}()
|
||
|
<-ctx.Done()
|
||
|
return ctx.Err()
|
||
|
}
|