keel/cache/memory/memory.go

211 lines
4.3 KiB
Go

package memory
import (
"context"
"fmt"
"strings"
"time"
"github.com/rusenask/keel/cache"
)
type requestType int
// Request types
const (
GET requestType = iota
SET
DELETE
EXPIRE
COPY
)
type (
// Value - value is stored together with access and creation time
Value struct {
ctime time.Time
atime time.Time
value []byte
}
// Cache - cache container with a map of values and defaults
Cache struct {
cache map[string]*Value
ctimeExpiry time.Duration // creation time
atimeExpiry time.Duration // access time
expiryTick time.Duration
requestChannel chan *request
}
request struct {
requestType
key string
value []byte
responseChannel chan *response
}
response struct {
error
existingValue []byte
mapCopy map[string][]byte
value []byte
}
)
func (c *Cache) isOld(v *Value) bool {
if (c.ctimeExpiry != time.Duration(0)) && (time.Now().Sub(v.ctime) > c.ctimeExpiry) {
return true
}
if (c.atimeExpiry != time.Duration(0)) && (time.Now().Sub(v.atime) > c.atimeExpiry) {
return true
}
return false
}
// NewMemoryCache - creates new cache
func NewMemoryCache(ctimeExpiry, atimeExpiry, expiryTick time.Duration) *Cache {
c := &Cache{
cache: make(map[string]*Value),
ctimeExpiry: ctimeExpiry,
atimeExpiry: atimeExpiry,
expiryTick: expiryTick,
requestChannel: make(chan *request),
}
go c.service()
if ctimeExpiry != time.Duration(0) || atimeExpiry != time.Duration(0) {
go c.expiryService()
}
return c
}
func (c *Cache) service() {
for {
req := <-c.requestChannel
resp := &response{}
switch req.requestType {
case GET:
val, ok := c.cache[req.key]
if !ok {
resp.error = cache.ErrNotFound
} else if c.isOld(val) {
resp.error = cache.ErrExpired
delete(c.cache, req.key)
} else {
// update atime
val.atime = time.Now()
c.cache[req.key] = val
resp.value = val.value
}
req.responseChannel <- resp
case SET:
c.cache[req.key] = &Value{
value: req.value,
ctime: time.Now(),
atime: time.Now(),
}
req.responseChannel <- resp
case DELETE:
delete(c.cache, req.key)
req.responseChannel <- resp
case EXPIRE:
for k, v := range c.cache {
if c.isOld(v) {
delete(c.cache, k)
}
}
// no response
case COPY:
resp.mapCopy = make(map[string][]byte)
for k, v := range c.cache {
resp.mapCopy[k] = v.value
}
req.responseChannel <- resp
default:
resp.error = fmt.Errorf("invalid request type: %v", req.requestType)
req.responseChannel <- resp
}
}
}
// Get - looks up value and returns it
func (c *Cache) Get(ctx context.Context, key string) ([]byte, error) {
respChannel := make(chan *response)
c.requestChannel <- &request{
requestType: GET,
key: key,
responseChannel: respChannel,
}
resp := <-respChannel
return resp.value, resp.error
}
// Put - sets key/string. Overwrites existing key
func (c *Cache) Put(ctx context.Context, key string, value []byte) error {
respChannel := make(chan *response)
c.requestChannel <- &request{
requestType: SET,
key: key,
value: value,
responseChannel: respChannel,
}
resp := <-respChannel
return resp.error
}
// Delete - deletes key
func (c *Cache) Delete(ctx context.Context, key string) error {
respChannel := make(chan *response)
c.requestChannel <- &request{
requestType: DELETE,
key: key,
responseChannel: respChannel,
}
resp := <-respChannel
return resp.error
}
// List all values for specified prefix
func (c *Cache) List(prefix string) ([][]byte, error) {
respChannel := make(chan *response)
c.requestChannel <- &request{
requestType: COPY,
responseChannel: respChannel,
}
resp := <-respChannel
var list [][]byte
for k, v := range resp.mapCopy {
if strings.HasPrefix(k, prefix) {
list = append(list, v)
}
}
return list, nil
}
// Copy - makes a copy of inmemory map
func (c *Cache) Copy() map[string][]byte {
respChannel := make(chan *response)
c.requestChannel <- &request{
requestType: COPY,
responseChannel: respChannel,
}
resp := <-respChannel
return resp.mapCopy
}
func (c *Cache) expiryService() {
ticker := time.NewTicker(c.expiryTick)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.requestChannel <- &request{
requestType: EXPIRE,
}
}
}
}