owpengram-server/internal/app/langpack/cache.go
A 1292540350 feat: sync langpack and auth updates
Sync telesrv commits 49f9bab and 04d9563 into the public mirror. Exclude private docs and runtime key material per sync rules.
2026-07-18 00:07:41 +08:00

238 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 返回 false 仅表示 load 期间发生过 flush调用方必须重载。
// 超大单项不进入缓存,但仍可安全返回给当前请求,因此返回 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...)
}