merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
|
|
@ -2,6 +2,7 @@ package postgres
|
|||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/readmodelcache"
|
||||
|
|
@ -12,6 +13,14 @@ type channelDialogCacheKey struct {
|
|||
channelID int64
|
||||
}
|
||||
|
||||
type channelDialogCacheEntry struct {
|
||||
dialog domain.ChannelDialog
|
||||
listVisible bool
|
||||
topMentioned bool
|
||||
topMediaUnread bool
|
||||
topUnreadProjected bool
|
||||
}
|
||||
|
||||
// ChannelDialogCache 缓存 viewer 作用域的频道 dialog 投影,由统一缓存原语
|
||||
// readmodelcache.Cache 承载(LRU 单条驱逐 / epoch 守卫 / singleflight 内建)。
|
||||
//
|
||||
|
|
@ -19,39 +28,59 @@ type channelDialogCacheKey struct {
|
|||
// dialog_light(viewer,channel) 三个 read model;ReadModelChangeListener 在写侧 NOTIFY 时
|
||||
// 失效对应键,重连时 flush。warm-from-list 经 put 回填(含 DefaultSendAs)。
|
||||
type ChannelDialogCache struct {
|
||||
cache *readmodelcache.Cache[channelDialogCacheKey, domain.ChannelDialog]
|
||||
cache *readmodelcache.Cache[channelDialogCacheKey, channelDialogCacheEntry]
|
||||
|
||||
indexMu sync.Mutex
|
||||
channelKeys map[int64]map[channelDialogCacheKey]struct{}
|
||||
}
|
||||
|
||||
func NewChannelDialogCache(max int) *ChannelDialogCache {
|
||||
cache := readmodelcache.New[channelDialogCacheKey, domain.ChannelDialog](readmodelcache.Config[channelDialogCacheKey, domain.ChannelDialog]{
|
||||
c := &ChannelDialogCache{channelKeys: make(map[int64]map[channelDialogCacheKey]struct{})}
|
||||
cache := readmodelcache.New[channelDialogCacheKey, channelDialogCacheEntry](readmodelcache.Config[channelDialogCacheKey, channelDialogCacheEntry]{
|
||||
MaxEntries: max,
|
||||
Clone: cloneChannelDialog,
|
||||
Clone: cloneChannelDialogCacheEntry,
|
||||
OnStore: c.indexEntry,
|
||||
OnRemove: c.unindexEntry,
|
||||
})
|
||||
if cache == nil {
|
||||
return nil
|
||||
}
|
||||
return &ChannelDialogCache{cache: cache}
|
||||
c.cache = cache
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) get(userID, channelID int64) (domain.ChannelDialog, bool) {
|
||||
if c == nil || userID == 0 || channelID == 0 {
|
||||
return domain.ChannelDialog{}, false
|
||||
}
|
||||
return c.cache.Peek(channelDialogCacheKey{userID: userID, channelID: channelID})
|
||||
entry, ok := c.cache.Peek(channelDialogCacheKey{userID: userID, channelID: channelID})
|
||||
return entry.dialog, ok
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) getListProjection(userID, channelID int64) (channelDialogCacheEntry, bool) {
|
||||
if c == nil || userID == 0 || channelID == 0 {
|
||||
return channelDialogCacheEntry{}, false
|
||||
}
|
||||
entry, ok := c.cache.Peek(channelDialogCacheKey{userID: userID, channelID: channelID})
|
||||
return entry, ok && entry.listVisible
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) getOrLoad(ctx context.Context, userID, channelID int64, load func() (domain.ChannelDialog, error)) (domain.ChannelDialog, error) {
|
||||
if c == nil || userID == 0 || channelID == 0 {
|
||||
return load()
|
||||
}
|
||||
return c.cache.GetOrLoad(ctx, channelDialogCacheKey{userID: userID, channelID: channelID}, load)
|
||||
entry, err := c.cache.GetOrLoad(ctx, channelDialogCacheKey{userID: userID, channelID: channelID}, func() (channelDialogCacheEntry, error) {
|
||||
dialog, err := load()
|
||||
return channelDialogCacheEntry{dialog: dialog}, err
|
||||
})
|
||||
return entry.dialog, err
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) put(dialog domain.ChannelDialog) {
|
||||
if c == nil || dialog.UserID == 0 || dialog.ChannelID == 0 {
|
||||
return
|
||||
}
|
||||
c.cache.Store(channelDialogCacheKey{userID: dialog.UserID, channelID: dialog.ChannelID}, dialog)
|
||||
c.cache.Store(channelDialogCacheKey{userID: dialog.UserID, channelID: dialog.ChannelID}, channelDialogCacheEntry{dialog: dialog})
|
||||
}
|
||||
|
||||
// cacheEpoch 在「列表暖写回」前快照 epoch;配合 putIfEpoch 堵住 warm-vs-invalidation
|
||||
|
|
@ -67,7 +96,30 @@ func (c *ChannelDialogCache) putIfEpoch(dialog domain.ChannelDialog, loadEpoch u
|
|||
if c == nil || dialog.UserID == 0 || dialog.ChannelID == 0 {
|
||||
return
|
||||
}
|
||||
c.cache.StoreIfEpoch(channelDialogCacheKey{userID: dialog.UserID, channelID: dialog.ChannelID}, dialog, loadEpoch)
|
||||
c.cache.StoreIfEpoch(channelDialogCacheKey{userID: dialog.UserID, channelID: dialog.ChannelID}, channelDialogCacheEntry{dialog: dialog}, loadEpoch)
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) putListProjectionIfEpoch(
|
||||
dialog domain.ChannelDialog,
|
||||
topMentioned bool,
|
||||
topMediaUnread bool,
|
||||
topUnreadProjected bool,
|
||||
loadEpoch uint64,
|
||||
) {
|
||||
if c == nil || dialog.UserID == 0 || dialog.ChannelID == 0 {
|
||||
return
|
||||
}
|
||||
c.cache.StoreIfEpoch(
|
||||
channelDialogCacheKey{userID: dialog.UserID, channelID: dialog.ChannelID},
|
||||
channelDialogCacheEntry{
|
||||
dialog: dialog,
|
||||
listVisible: true,
|
||||
topMentioned: topMentioned,
|
||||
topMediaUnread: topMediaUnread,
|
||||
topUnreadProjected: topUnreadProjected,
|
||||
},
|
||||
loadEpoch,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) delete(userID, channelID int64) {
|
||||
|
|
@ -81,7 +133,14 @@ func (c *ChannelDialogCache) deleteChannel(channelID int64) {
|
|||
if c == nil || channelID == 0 {
|
||||
return
|
||||
}
|
||||
c.cache.InvalidateWhere(func(k channelDialogCacheKey) bool { return k.channelID == channelID })
|
||||
c.indexMu.Lock()
|
||||
indexed := c.channelKeys[channelID]
|
||||
keys := make([]channelDialogCacheKey, 0, len(indexed))
|
||||
for key := range indexed {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
c.indexMu.Unlock()
|
||||
c.cache.Invalidate(keys...)
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) flush() {
|
||||
|
|
@ -89,12 +148,36 @@ func (c *ChannelDialogCache) flush() {
|
|||
return
|
||||
}
|
||||
c.cache.Flush()
|
||||
c.indexMu.Lock()
|
||||
c.channelKeys = make(map[int64]map[channelDialogCacheKey]struct{})
|
||||
c.indexMu.Unlock()
|
||||
}
|
||||
|
||||
func cloneChannelDialog(dialog domain.ChannelDialog) domain.ChannelDialog {
|
||||
if dialog.DefaultSendAs != nil {
|
||||
peer := *dialog.DefaultSendAs
|
||||
dialog.DefaultSendAs = &peer
|
||||
func (c *ChannelDialogCache) indexEntry(key channelDialogCacheKey, _ channelDialogCacheEntry) {
|
||||
c.indexMu.Lock()
|
||||
keys := c.channelKeys[key.channelID]
|
||||
if keys == nil {
|
||||
keys = make(map[channelDialogCacheKey]struct{})
|
||||
c.channelKeys[key.channelID] = keys
|
||||
}
|
||||
return dialog
|
||||
keys[key] = struct{}{}
|
||||
c.indexMu.Unlock()
|
||||
}
|
||||
|
||||
func (c *ChannelDialogCache) unindexEntry(key channelDialogCacheKey, _ channelDialogCacheEntry) {
|
||||
c.indexMu.Lock()
|
||||
keys := c.channelKeys[key.channelID]
|
||||
delete(keys, key)
|
||||
if len(keys) == 0 {
|
||||
delete(c.channelKeys, key.channelID)
|
||||
}
|
||||
c.indexMu.Unlock()
|
||||
}
|
||||
|
||||
func cloneChannelDialogCacheEntry(entry channelDialogCacheEntry) channelDialogCacheEntry {
|
||||
if entry.dialog.DefaultSendAs != nil {
|
||||
peer := *entry.dialog.DefaultSendAs
|
||||
entry.dialog.DefaultSendAs = &peer
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue