merged from gramsrv upstream

This commit is contained in:
onysd 2026-09-01 12:06:31 +03:00
parent 79c64ee916
commit 21a0856587
651 changed files with 54774 additions and 4590 deletions

View file

@ -32,6 +32,11 @@ type Config[K comparable, V any] struct {
// MaxEntries 是 LRU 上界。<=0 时 New 返回 nil(等价"禁用缓存",沿用各处
// New*Cache(max<=0)->nil 的惯例;所有方法对 nil 安全,退化为直接 load)。
MaxEntries int
// MaxWeight 是可选的第二容量边界。>0 时每个值由 Weight 计算权重,缓存同时
// 满足 MaxEntries 与 MaxWeight单项超过上限时本次仍可返回但不驻留。
MaxWeight int64
// Weight 计算一个缓存值的相对占用;仅 MaxWeight>0 时使用。nil 时每项权重为 1。
Weight func(V) int64
// TTL 仅作安全兜底(漏掉的带外写)。0 = 纯事件驱动,无时间过期。
TTL time.Duration
// Clone 在 store 与返回两个边界上对值做深拷贝,隔离调用方与缓存项的别名突变。
@ -43,6 +48,10 @@ type Config[K comparable, V any] struct {
// Now 注入时钟,仅用于 TTL 过期判断;nil 时默认 time.Now。生产一律留空,
// 测试可注入假时钟以确定地推进 TTL。
Now func() time.Time
// OnStore/OnRemove 供依赖倒排索引同步生命周期。回调在缓存锁内执行,
// 不得回调本 Cache 或阻塞;收到的 value 是缓存持有的 immutable clone。
OnStore func(K, V)
OnRemove func(K, V)
}
type lruEntry[K comparable, V any] struct {
@ -50,6 +59,7 @@ type lruEntry[K comparable, V any] struct {
value V
hash int64
expireAt time.Time // 零值 = 不过期
weight int64
}
// Cache 是泛型 read-model 缓存。零值不可用,必须经 New 构造。nil *Cache 合法:
@ -59,12 +69,39 @@ type Cache[K comparable, V any] struct {
ll *list.List // LRU 顺序,Front=最近使用
items map[K]*list.Element
cap int
maxWeight int64
weight int64
ttl time.Duration
epoch uint64
sf singleflight.Group
clone func(V) V
weigh func(V) int64
keyString func(K) string
now func() time.Time
onStore func(K, V)
onRemove func(K, V)
// batchFlights coordinates individual keys across overlapping concurrent
// GetOrLoadBatch calls. A singleflight key for the whole input slice cannot
// coalesce {1,2,3} with {2,3,4}; tracking the misses per key lets the first
// caller own 2/3 while the second still loads 4 in its own backend batch.
batchMu sync.Mutex
batchFlights map[batchFlightKey[K]]*batchFlight[V]
}
type batchFlightKey[K comparable] struct {
key K
hash int64
cacheable bool
epoch uint64
}
type batchFlight[V any] struct {
done chan struct{}
value V
ok bool
err error
retry bool
}
// New 构造一个 Cache。MaxEntries<=0 时返回 nil(禁用缓存,沿用既有惯例)。
@ -81,13 +118,18 @@ func New[K comparable, V any](cfg Config[K, V]) *Cache[K, V] {
now = time.Now
}
return &Cache[K, V]{
ll: list.New(),
items: make(map[K]*list.Element, initialMapHint(cfg.MaxEntries)),
cap: cfg.MaxEntries,
ttl: cfg.TTL,
clone: cfg.Clone,
keyString: keyString,
now: now,
ll: list.New(),
items: make(map[K]*list.Element, initialMapHint(cfg.MaxEntries)),
cap: cfg.MaxEntries,
maxWeight: cfg.MaxWeight,
ttl: cfg.TTL,
clone: cfg.Clone,
weigh: cfg.Weight,
keyString: keyString,
now: now,
onStore: cfg.OnStore,
onRemove: cfg.OnRemove,
batchFlights: make(map[batchFlightKey[K]]*batchFlight[V]),
}
}
@ -189,19 +231,38 @@ func (c *Cache[K, V]) storeIfEpoch(key K, v V, hash int64, loadEpoch uint64) boo
}
func (c *Cache[K, V]) storeLocked(key K, v V, hash int64) {
weight := c.valueWeight(v)
if c.maxWeight > 0 && weight > c.maxWeight {
if el, ok := c.items[key]; ok {
c.removeElement(el)
}
return
}
if el, ok := c.items[key]; ok {
ent := el.Value.(*lruEntry[K, V])
if c.onRemove != nil {
c.onRemove(ent.key, ent.value)
}
c.weight -= ent.weight
ent.value = c.cloneValue(v)
ent.hash = hash
ent.expireAt = c.expireAt()
ent.weight = weight
c.weight += weight
if c.onStore != nil {
c.onStore(ent.key, ent.value)
}
c.ll.MoveToFront(el)
c.evictOverflow()
return
}
ent := &lruEntry[K, V]{key: key, value: c.cloneValue(v), hash: hash, expireAt: c.expireAt()}
ent := &lruEntry[K, V]{key: key, value: c.cloneValue(v), hash: hash, expireAt: c.expireAt(), weight: weight}
c.items[key] = c.ll.PushFront(ent)
if c.ll.Len() > c.cap {
c.evictOldest()
c.weight += weight
if c.onStore != nil {
c.onStore(ent.key, ent.value)
}
c.evictOverflow()
}
// Store 把一个已在手的值写入缓存(warm-from-list 路径)。不自增 epoch:它不是失效,
@ -292,31 +353,58 @@ func (c *Cache[K, V]) GetOrLoadBatch(
if len(missing) == 0 {
return out, nil
}
missingKeys := make([]K, len(missing))
for i := range missing {
missingKeys[i] = missing[i].key
waits, owned := c.claimBatchFlights(missing, loadEpoch)
if len(owned) > 0 {
ownedKeys := make([]K, len(owned))
for i := range owned {
ownedKeys[i] = owned[i].miss.key
}
loaded, loadErr := loadMissing(ctx, ownedKeys)
retry := false
if loadErr == nil {
entries := make([]batchStoreEntry[K, V], 0, len(owned))
for _, owner := range owned {
value, ok := loaded[owner.miss.key]
if ok && owner.miss.cacheable {
entries = append(entries, batchStoreEntry[K, V]{
key: owner.miss.key, value: value, hash: owner.miss.hash,
})
}
}
retry = !c.storeBatchIfEpoch(entries, loadEpoch)
}
for _, owner := range owned {
value, ok := loaded[owner.miss.key]
c.completeBatchFlight(owner.key, owner.flight, value, ok, loadErr, retry)
}
}
loaded, err := loadMissing(ctx, missingKeys)
if err != nil {
return nil, err
retry := false
for _, wait := range waits {
select {
case <-wait.flight.done:
case <-ctx.Done():
return nil, ctx.Err()
}
if wait.flight.err != nil {
return nil, wait.flight.err
}
if wait.flight.retry {
retry = true
continue
}
if wait.flight.ok {
out[wait.miss.key] = c.cloneValue(wait.flight.value)
}
}
if c.cacheEpoch() != loadEpoch {
// 失效在批量 load 期间到达:重试整趟,避免用 pre-invalidation 数据遮蔽它。
if retry {
// 失效在任一 owner 的批量 load 期间到达:所有参与者重查,
// 不让 pre-invalidation 的共享 flight 值越过 epoch 边界。
if err := ctx.Err(); err != nil {
return nil, err
}
continue
}
for _, m := range missing {
v, ok := loaded[m.key]
if !ok {
continue
}
out[m.key] = v
if m.cacheable {
c.storeIfEpoch(m.key, v, m.hash, loadEpoch)
}
}
return out, nil
}
}
@ -327,6 +415,76 @@ type batchMiss[K comparable] struct {
cacheable bool
}
type batchWait[K comparable, V any] struct {
miss batchMiss[K]
key batchFlightKey[K]
flight *batchFlight[V]
}
type batchStoreEntry[K comparable, V any] struct {
key K
value V
hash int64
}
func (c *Cache[K, V]) claimBatchFlights(
missing []batchMiss[K],
epoch uint64,
) (waits []batchWait[K, V], owned []batchWait[K, V]) {
waits = make([]batchWait[K, V], 0, len(missing))
owned = make([]batchWait[K, V], 0, len(missing))
c.batchMu.Lock()
for _, miss := range missing {
key := batchFlightKey[K]{key: miss.key, hash: miss.hash, cacheable: miss.cacheable, epoch: epoch}
flight, found := c.batchFlights[key]
wait := batchWait[K, V]{miss: miss, key: key, flight: flight}
if !found {
flight = &batchFlight[V]{done: make(chan struct{})}
c.batchFlights[key] = flight
wait.flight = flight
owned = append(owned, wait)
}
waits = append(waits, wait)
}
c.batchMu.Unlock()
return waits, owned
}
func (c *Cache[K, V]) completeBatchFlight(
key batchFlightKey[K],
flight *batchFlight[V],
value V,
ok bool,
err error,
retry bool,
) {
c.batchMu.Lock()
flight.value = c.cloneValue(value)
flight.ok = ok
flight.err = err
flight.retry = retry
if current := c.batchFlights[key]; current == flight {
delete(c.batchFlights, key)
}
close(flight.done)
c.batchMu.Unlock()
}
// storeBatchIfEpoch makes the write side of one batch atomic with respect to
// invalidation. Besides avoiding partial warm state, this gives every waiter
// one unambiguous retry decision for the batch generation it joined.
func (c *Cache[K, V]) storeBatchIfEpoch(entries []batchStoreEntry[K, V], expected uint64) bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.epoch != expected {
return false
}
for _, entry := range entries {
c.storeLocked(entry.key, entry.value, entry.hash)
}
return true
}
func dedupeKeys[K comparable](keys []K) []K {
seen := make(map[K]struct{}, len(keys))
out := make([]K, 0, len(keys))
@ -391,6 +549,26 @@ func (c *Cache[K, V]) InvalidateWhere(pred func(K) bool) {
c.mu.Unlock()
}
// InvalidateWhereValue is the dependency-aware form of InvalidateWhere. It is
// intended for bounded composite snapshots whose invalidation key is carried
// by the immutable cached value (for example channel_id -> owner dialog page).
// pred runs under the cache lock and therefore must be fast and must not call
// back into this cache.
func (c *Cache[K, V]) InvalidateWhereValue(pred func(K, V) bool) {
if c == nil || pred == nil {
return
}
c.mu.Lock()
c.epoch++
for key, el := range c.items {
ent := el.Value.(*lruEntry[K, V])
if pred(key, ent.value) {
c.removeElement(el)
}
}
c.mu.Unlock()
}
// Flush 清空缓存并自增 epoch(监听器断线重连兜底)。
func (c *Cache[K, V]) Flush() {
if c == nil {
@ -398,8 +576,15 @@ func (c *Cache[K, V]) Flush() {
}
c.mu.Lock()
c.epoch++
if c.onRemove != nil {
for el := c.ll.Front(); el != nil; el = el.Next() {
ent := el.Value.(*lruEntry[K, V])
c.onRemove(ent.key, ent.value)
}
}
c.ll.Init()
c.items = make(map[K]*list.Element, initialMapHint(c.cap))
c.weight = 0
c.mu.Unlock()
}
@ -414,6 +599,19 @@ func (c *Cache[K, V]) Len() int {
return n
}
// Weight returns the current aggregate configured weight. It is intended for
// bounded observability and tests; callers must not use it as a correctness
// input because Weight is deliberately an approximation chosen by each cache.
func (c *Cache[K, V]) Weight() int64 {
if c == nil {
return 0
}
c.mu.Lock()
weight := c.weight
c.mu.Unlock()
return weight
}
func (c *Cache[K, V]) cacheEpoch() uint64 {
c.mu.Lock()
e := c.epoch
@ -438,9 +636,34 @@ func (c *Cache[K, V]) evictOldest() {
}
}
func (c *Cache[K, V]) evictOverflow() {
for c.ll.Len() > c.cap || (c.maxWeight > 0 && c.weight > c.maxWeight) {
if c.ll.Back() == nil {
return
}
c.evictOldest()
}
}
func (c *Cache[K, V]) removeElement(el *list.Element) {
ent := el.Value.(*lruEntry[K, V])
if c.onRemove != nil {
c.onRemove(ent.key, ent.value)
}
c.weight -= ent.weight
c.ll.Remove(el)
delete(c.items, el.Value.(*lruEntry[K, V]).key)
delete(c.items, ent.key)
}
func (c *Cache[K, V]) valueWeight(v V) int64 {
if c.maxWeight <= 0 || c.weigh == nil {
return 1
}
weight := c.weigh(v)
if weight <= 0 {
return 1
}
return weight
}
func (c *Cache[K, V]) cloneValue(v V) V {