Ctrl K
ComunidadGuías…

internal/app/langpack/cache.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package langpack

import (
	"container/list"
	"sync"

	"telesrv/internal/domain"
)

const (
	defaultLangPackCacheMaxBytes       = int64(128 << 20)
	defaultLangPackCacheMaxEntries     = 256
	defaultLanguageListCacheMaxEntries = 32
	langPackStringValueHeaderBytes     = int64(144)
	langPackFixedHeaderBytes           = int64(80)
)

type langPackCacheKind uint8

const (
	langPackCacheRaw langPackCacheKind = iota
	langPackCacheEffective
)

type langPackCacheKey struct {
	pack string
	code string
	kind langPackCacheKind
}

func (k langPackCacheKey) singleflightKey() string {
	return string(rune(k.kind)) + "\x00" + k.pack + "\x00" + k.code
}

type langPackCache struct {
	mu         sync.Mutex
	maxBytes   int64
	maxEntries int
	usedBytes  int64
	epoch      uint64
	ll         *list.List
	items      map[langPackCacheKey]*list.Element
}

type langPackCacheEntry struct {
	key  langPackCacheKey
	pack domain.LangPack
	size int64
}

func newLangPackCache(maxBytes int64, maxEntries int) *langPackCache {
	if maxBytes <= 0 || maxEntries <= 0 {
		return nil
	}
	return &langPackCache{
		maxBytes:   maxBytes,
		maxEntries: maxEntries,
		ll:         list.New(),
		items:      make(map[langPackCacheKey]*list.Element),
	}
}

func (c *langPackCache) get(key langPackCacheKey) (domain.LangPack, bool) {
	if c == nil {
		return domain.LangPack{}, false
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	element, ok := c.items[key]
	if !ok {
		return domain.LangPack{}, false
	}
	c.ll.MoveToFront(element)
	return cloneLangPack(element.Value.(*langPackCacheEntry).pack), true
}

func (c *langPackCache) loadEpoch() uint64 {
	if c == nil {
		return 0
	}
	c.mu.Lock()
	epoch := c.epoch
	c.mu.Unlock()
	return epoch
}

// putIfEpoch returns false only when a flush happened during load; the caller must reload.
// An oversized item is not cached but can still be safely returned to the current request, so it returns true.
func (c *langPackCache) putIfEpoch(key langPackCacheKey, pack domain.LangPack, loadEpoch uint64) bool {
	if c == nil {
		return true
	}
	size := estimateLangPackBytes(pack)
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.epoch != loadEpoch {
		return false
	}
	if existing, ok := c.items[key]; ok {
		c.remove(existing)
	}
	if size > c.maxBytes {
		return true
	}
	entry := &langPackCacheEntry{key: key, pack: cloneLangPack(pack), size: size}
	c.items[key] = c.ll.PushFront(entry)
	c.usedBytes += size
	for c.usedBytes > c.maxBytes || c.ll.Len() > c.maxEntries {
		oldest := c.ll.Back()
		if oldest == nil {
			break
		}
		c.remove(oldest)
	}
	return true
}

func (c *langPackCache) flush() {
	if c == nil {
		return
	}
	c.mu.Lock()
	c.epoch++
	c.usedBytes = 0
	c.ll.Init()
	clear(c.items)
	c.mu.Unlock()
}

func (c *langPackCache) remove(element *list.Element) {
	entry := element.Value.(*langPackCacheEntry)
	delete(c.items, entry.key)
	c.ll.Remove(element)
	c.usedBytes -= entry.size
}

func estimateLangPackBytes(pack domain.LangPack) int64 {
	size := langPackFixedHeaderBytes + int64(len(pack.LangPack)+len(pack.LangCode))
	size += int64(len(pack.Strings)) * langPackStringValueHeaderBytes
	for _, item := range pack.Strings {
		size += int64(len(item.Key) + len(item.Value) + len(item.ZeroValue) + len(item.OneValue) +
			len(item.TwoValue) + len(item.FewValue) + len(item.ManyValue) + len(item.OtherValue))
	}
	return size
}

func cloneLangPack(pack domain.LangPack) domain.LangPack {
	pack.Strings = append([]domain.LangPackString(nil), pack.Strings...)
	return pack
}

type languageListCache struct {
	mu         sync.Mutex
	maxEntries int
	epoch      uint64
	ll         *list.List
	items      map[string]*list.Element
}

type languageListCacheEntry struct {
	pack      string
	languages []domain.LangPackLanguage
}

func newLanguageListCache(maxEntries int) *languageListCache {
	if maxEntries <= 0 {
		return nil
	}
	return &languageListCache{
		maxEntries: maxEntries,
		ll:         list.New(),
		items:      make(map[string]*list.Element),
	}
}

func (c *languageListCache) get(pack string) ([]domain.LangPackLanguage, bool) {
	if c == nil {
		return nil, false
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	element, ok := c.items[pack]
	if !ok {
		return nil, false
	}
	c.ll.MoveToFront(element)
	return cloneLanguages(element.Value.(*languageListCacheEntry).languages), true
}

func (c *languageListCache) loadEpoch() uint64 {
	if c == nil {
		return 0
	}
	c.mu.Lock()
	epoch := c.epoch
	c.mu.Unlock()
	return epoch
}

func (c *languageListCache) putIfEpoch(pack string, languages []domain.LangPackLanguage, loadEpoch uint64) bool {
	if c == nil {
		return true
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.epoch != loadEpoch {
		return false
	}
	if existing, ok := c.items[pack]; ok {
		c.ll.Remove(existing)
		delete(c.items, pack)
	}
	entry := &languageListCacheEntry{pack: pack, languages: cloneLanguages(languages)}
	c.items[pack] = c.ll.PushFront(entry)
	if c.ll.Len() > c.maxEntries {
		oldest := c.ll.Back()
		if oldest != nil {
			delete(c.items, oldest.Value.(*languageListCacheEntry).pack)
			c.ll.Remove(oldest)
		}
	}
	return true
}

func (c *languageListCache) flush() {
	if c == nil {
		return
	}
	c.mu.Lock()
	c.epoch++
	c.ll.Init()
	clear(c.items)
	c.mu.Unlock()
}

func cloneLanguages(languages []domain.LangPackLanguage) []domain.LangPackLanguage {
	return append([]domain.LangPackLanguage(nil), languages...)
}