Files
im_server/server/store/config_cache.go
T

180 lines
4.4 KiB
Go

package store
import (
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/tinode/chat/server/store/types"
)
const configCacheTTL = time.Minute
type configCacheEntry[T any] struct {
value T
expiresAt time.Time
}
type configCache[T any] struct {
mu sync.RWMutex
items map[string]configCacheEntry[T]
}
func newConfigCache[T any]() *configCache[T] {
return &configCache[T]{items: make(map[string]configCacheEntry[T])}
}
func (c *configCache[T]) get(key string) (T, bool) {
var zero T
c.mu.RLock()
entry, ok := c.items[key]
c.mu.RUnlock()
if !ok || time.Now().After(entry.expiresAt) {
return zero, false
}
return entry.value, true
}
func (c *configCache[T]) set(key string, value T) {
c.mu.Lock()
c.items[key] = configCacheEntry[T]{value: value, expiresAt: time.Now().Add(configCacheTTL)}
c.mu.Unlock()
}
func (c *configCache[T]) delete(key string) {
c.mu.Lock()
delete(c.items, key)
c.mu.Unlock()
}
func (c *configCache[T]) clear() {
c.mu.Lock()
c.items = make(map[string]configCacheEntry[T])
c.mu.Unlock()
}
var (
tenantByCodeCache = newConfigCache[*types.Tenant]()
tenantByIDCache = newConfigCache[*types.Tenant]()
registrationSMSCache = newConfigCache[bool]()
thirdPartyConfigCache = newConfigCache[map[string]string]()
appReleaseConfigCache = newConfigCache[*types.AppRelease]()
moderationProviderCache = newConfigCache[*types.ModerationProviderConfig]()
moderationPolicyCache = newConfigCache[*types.ModerationPolicy]()
moderationWordsCache = newConfigCache[*types.SensitiveWordSet]()
securityPolicyCache = newConfigCache[*types.SecurityPolicy]()
realNamePolicyCache = newConfigCache[*types.RealNamePolicy]()
thirdPartyConfigAllKeys = "*"
)
func InvalidateSecurityPolicyCache(tenantCode string) {
if strings.TrimSpace(tenantCode) == "" {
securityPolicyCache.clear()
return
}
securityPolicyCache.delete(strings.TrimSpace(tenantCode))
}
func InvalidateModerationConfigCache(tenantCode string, provider, policy, words bool) {
if provider {
moderationProviderCache.clear()
}
if policy {
if strings.TrimSpace(tenantCode) == "" {
moderationPolicyCache.clear()
} else {
moderationPolicyCache.delete(strings.TrimSpace(tenantCode))
}
}
if words {
if strings.TrimSpace(tenantCode) == "" {
moderationWordsCache.clear()
} else {
moderationWordsCache.delete(strings.TrimSpace(tenantCode))
}
}
}
func cloneTenant(t *types.Tenant) *types.Tenant {
if t == nil {
return nil
}
cp := *t
return &cp
}
// InvalidateAppReleaseConfigCache clears global client release policies.
func InvalidateAppReleaseConfigCache() {
appReleaseConfigCache.clear()
}
func cloneStringMap(src map[string]string) map[string]string {
if src == nil {
return nil
}
dst := make(map[string]string, len(src))
for k, v := range src {
dst[k] = v
}
return dst
}
func thirdPartyCacheKey(group string, keys ...string) string {
group = strings.TrimSpace(group)
if len(keys) == 0 {
return group + ":" + thirdPartyConfigAllKeys
}
normalized := make([]string, 0, len(keys))
for _, key := range keys {
if key = strings.TrimSpace(key); key != "" {
normalized = append(normalized, key)
}
}
sort.Strings(normalized)
return group + ":" + strings.Join(normalized, ",")
}
// InvalidateTenantConfigCache clears cached tenant registry/config records. An
// empty selector clears all tenant records.
func InvalidateTenantConfigCache(code string, id types.TenantID) {
if strings.TrimSpace(code) != "" {
tenantByCodeCache.delete(strings.TrimSpace(code))
} else {
tenantByCodeCache.clear()
}
if id.IsValid() {
tenantByIDCache.delete(formatTenantIDForCache(id))
realNamePolicyCache.delete(formatTenantIDForCache(id))
} else if strings.TrimSpace(code) == "" {
tenantByIDCache.clear()
realNamePolicyCache.clear()
}
}
// InvalidateRegistrationConfigCache clears cached global registration config.
func InvalidateRegistrationConfigCache() {
registrationSMSCache.clear()
}
// InvalidateThirdPartyConfigCache clears cached third-party config.
func InvalidateThirdPartyConfigCache(group string) {
if strings.TrimSpace(group) == "" {
thirdPartyConfigCache.clear()
return
}
prefix := strings.TrimSpace(group) + ":"
thirdPartyConfigCache.mu.Lock()
for key := range thirdPartyConfigCache.items {
if strings.HasPrefix(key, prefix) {
delete(thirdPartyConfigCache.items, key)
}
}
thirdPartyConfigCache.mu.Unlock()
}
func formatTenantIDForCache(id types.TenantID) string {
return strconv.FormatInt(int64(id), 10)
}