Sobes.tech
Senior

type TTLCache interface { Set(k string, v any, ttl time.Duration) Get(k string) (any, bool) Del(k string) bool } // Should not leak memory // get should not return expired // Implementation

sobes.tech AI

Answer from AI

To implement TTLCache in Go that prevents memory leaks and does not return expired values, you can use a structure with a mutex for thread safety, a map to store values, and an expiration time for each key. A mechanism to clean up expired items, such as a periodic goroutine, is also needed.

Example implementation:

package ttlcache

import (
    "sync"
    "time"
)

type cacheItem struct {
    value      any
    expiration int64 // unix nano timestamp
}

type TTLCache interface {
    Set(k string, v any, ttl time.Duration)
    Get(k string) (any, bool)
    Del(k string) bool
}

type ttlCache struct {
    mu    sync.RWMutex
    items map[string]cacheItem
    quit  chan struct{}
}

func NewTTLCache(cleanupInterval time.Duration) TTLCache {
    c := &ttlCache{
        items: make(map[string]cacheItem),
        quit:  make(chan struct{}),
    }
    go c.cleanupLoop(cleanupInterval)
    return c
}

func (c *ttlCache) Set(k string, v any, ttl time.Duration) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[k] = cacheItem{
        value:      v,
        expiration: time.Now().Add(ttl).UnixNano(),
    }
}

func (c *ttlCache) Get(k string) (any, bool) {
    c.mu.RLock()
    item, found := c.items[k]
    c.mu.RUnlock()
    if !found {
        return nil, false
    }
    if time.Now().UnixNano() > item.expiration {
        // item expired, delete
        c.mu.Lock()
        delete(c.items, k)
        c.mu.Unlock()
        return nil, false
    }
    return item.value, true
}

func (c *ttlCache) Del(k string) bool {
    c.mu.Lock()
    defer c.mu.Unlock()
    if _, found := c.items[k]; found {
        delete(c.items, k)
        return true
    }
    return false
}

func (c *ttlCache) cleanupLoop(interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            now := time.Now().UnixNano()
            c.mu.Lock()
            for k, item := range c.items {
                if now > item.expiration {
                    delete(c.items, k)
                }
            }
            c.mu.Unlock()
        case <-c.quit:
            return
        }
    }
}

func (c *ttlCache) Close() {
    close(c.quit)
}

In this implementation:

  • When setting a value, an expiration time is stored.
  • When getting a value, it checks if the item has expired; if so, it deletes it and returns false.
  • There is a goroutine that periodically cleans up expired entries to prevent memory leaks.
  • RWMutex is used for thread safety.
type TTLCache interface { Set(k string, v any, ttl… - sobes.tech