Sync telesrv commits 49f9bab and 04d9563 into the public mirror. Exclude private docs and runtime key material per sync rules.
238 lines
5.2 KiB
Go
238 lines
5.2 KiB
Go
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...)
|
||
}
|