Browse Source

Add TTL cache

Dmitriy Gnatenko 5 months ago
parent
commit
9c20f6f267
2 changed files with 132 additions and 0 deletions
  1. 95 0
      cache/ttl_memory_cache/cache.go
  2. 37 0
      cache/ttl_memory_cache/cache_config.go

+ 95 - 0
cache/ttl_memory_cache/cache.go

@@ -0,0 +1,95 @@
+package ttl_memory_cache
+
+import (
+	"sync"
+	"time"
+)
+
+type Cache struct {
+	expiration      *time.Duration
+	cleanupInterval *time.Duration
+
+	sync.RWMutex
+	items map[string]Item
+}
+
+type Item struct {
+	Value     interface{}
+	ExpiredAt *time.Time
+}
+
+func NewCache(c Config) *Cache {
+	cache := Cache{
+		items:           make(map[string]Item),
+		expiration:      c.expiration,
+		cleanupInterval: c.cleanupInterval,
+	}
+
+	if c.cleanupInterval != nil {
+		go cache.cleanupWorker()
+	}
+
+	return &cache
+}
+
+func (c *Cache) Set(key string, value interface{}, expiration *time.Duration) {
+	item := Item{
+		Value: value,
+	}
+
+	if expiration != nil {
+		expiredAt := time.Now().Add(*expiration)
+		item.ExpiredAt = &expiredAt
+	} else if c.expiration != nil {
+		expiredAt := time.Now().Add(*c.expiration)
+		item.ExpiredAt = &expiredAt
+	}
+
+	c.Lock()
+	defer c.Unlock()
+
+	c.items[key] = item
+}
+
+func (c *Cache) Get(key string) (interface{}, bool) {
+	c.RLock()
+	defer c.RUnlock()
+
+	item, found := c.items[key]
+	if !found {
+		return nil, false
+	}
+
+	if item.ExpiredAt != nil && time.Now().After(*item.ExpiredAt) {
+		c.Delete(key)
+		return nil, false
+	}
+
+	return item.Value, true
+}
+
+func (c *Cache) Delete(key string) {
+	c.Lock()
+	defer c.Unlock()
+
+	if _, found := c.items[key]; found {
+		delete(c.items, key)
+	}
+}
+
+func (c *Cache) cleanupWorker() {
+	for {
+		<-time.After(*c.cleanupInterval)
+
+		if len(c.items) == 0 {
+			return
+		}
+
+		now := time.Now()
+		for key, item := range c.items {
+			if item.ExpiredAt != nil && now.After(*item.ExpiredAt) {
+				c.Delete(key)
+			}
+		}
+	}
+}

+ 37 - 0
cache/ttl_memory_cache/cache_config.go

@@ -0,0 +1,37 @@
+package ttl_memory_cache
+
+import "time"
+
+type Config struct {
+	expiration      *time.Duration
+	cleanupInterval *time.Duration
+}
+
+type ConfigOption func(*Config)
+
+type ConfigOptions []ConfigOption
+
+func (s *ConfigOptions) Add(option ConfigOption) {
+	*s = append(*s, option)
+}
+
+func NewConfig(opts ...ConfigOption) Config {
+	c := &Config{}
+	for _, opt := range opts {
+		opt(c)
+	}
+
+	return *c
+}
+
+func WithExpiration(expiration time.Duration) ConfigOption {
+	return func(s *Config) {
+		s.expiration = &expiration
+	}
+}
+
+func WithCleanupInterval(cleanupInterval time.Duration) ConfigOption {
+	return func(s *Config) {
+		s.cleanupInterval = &cleanupInterval
+	}
+}