fix: harden single-machine load handling
This commit is contained in:
@@ -3,6 +3,7 @@ package store
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -443,6 +444,34 @@ func TestInvalidateGroupSettingsUsesTopicCacheKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGroupMuteUsesPositiveAndNegativeCache(t *testing.T) {
|
||||
oldRuntime := groupMsgRuntime
|
||||
runtime := newTestGroupMessageRuntime()
|
||||
groupMsgRuntime = runtime
|
||||
t.Cleanup(func() { groupMsgRuntime = oldRuntime })
|
||||
|
||||
tenantID := types.TenantID(7)
|
||||
topic := "grpAlpha"
|
||||
uid := types.Uid(42)
|
||||
cacheID := groupMemberCacheID(topic, uid)
|
||||
expires := time.Now().UTC().Add(time.Minute)
|
||||
want := &types.GroupMute{TenantID: tenantID, Topic: topic, UserID: uid, Status: types.GroupMuteStatusActive, ExpiresAt: &expires}
|
||||
if err := CacheObject(tenantID, cacheNamespaceGroupMute, cacheID, groupMuteCacheValue{Found: true, Mute: want}); err != nil {
|
||||
t.Fatalf("cache positive mute: %v", err)
|
||||
}
|
||||
got, err := (groupBusinessMapper{}).GetMute(tenantID, topic, uid)
|
||||
if err != nil || got == nil || got.UserID != uid || got.Status != types.GroupMuteStatusActive {
|
||||
t.Fatalf("positive cache result: mute=%#v err=%v", got, err)
|
||||
}
|
||||
|
||||
if err = CacheObject(tenantID, cacheNamespaceGroupMute, cacheID, groupMuteCacheValue{}); err != nil {
|
||||
t.Fatalf("cache negative mute: %v", err)
|
||||
}
|
||||
if got, err = (groupBusinessMapper{}).GetMute(tenantID, topic, uid); !errors.Is(err, types.ErrNotFound) || got != nil {
|
||||
t.Fatalf("negative cache result: mute=%#v err=%v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicAndSubscriptionCacheTTLIsShort(t *testing.T) {
|
||||
runtime := &redisGroupMessageRuntime{cfg: redisConfig{CacheTTL: 300, ShortTTL: 600}}
|
||||
for _, namespace := range []string{cacheNamespaceTopic, cacheNamespaceSub} {
|
||||
|
||||
+53
-3
@@ -142,6 +142,7 @@ const (
|
||||
cacheNamespaceSub = "sub"
|
||||
cacheNamespaceGroupSettings = "group_settings"
|
||||
cacheNamespaceGroupMember = "group_member:v2"
|
||||
cacheNamespaceGroupMute = "group_mute:v1"
|
||||
cacheNamespaceVerification = "verification"
|
||||
cacheNamespaceActionToken = "action_token"
|
||||
cacheNamespaceFriends = "friends"
|
||||
@@ -2422,11 +2423,32 @@ func (groupBusinessMapper) GetMute(tenantID types.TenantID, topic string, uid ty
|
||||
if strings.TrimSpace(topic) == "" || uid.IsZero() {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
topic = strings.TrimSpace(topic)
|
||||
cacheID := groupMemberCacheID(topic, uid)
|
||||
var cached groupMuteCacheValue
|
||||
if ok, err := GetCachedObject(tenantID, cacheNamespaceGroupMute, cacheID, &cached); err != nil {
|
||||
logs.Warn.Printf("cache group mute[%s]: read failed: %v", cacheID, err)
|
||||
_ = DeleteCachedObject(tenantID, cacheNamespaceGroupMute, cacheID)
|
||||
} else if ok {
|
||||
if !cached.Found || cached.Mute == nil {
|
||||
return nil, types.ErrNotFound
|
||||
}
|
||||
if cached.Mute.ExpiresAt == nil || cached.Mute.ExpiresAt.After(time.Now().UTC()) {
|
||||
return cached.Mute, nil
|
||||
}
|
||||
_ = DeleteCachedObject(tenantID, cacheNamespaceGroupMute, cacheID)
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantGroupMuteGet(tenantID, strings.TrimSpace(topic), uid)
|
||||
mute, err := tenantAdp.TenantGroupMuteGet(tenantID, topic, uid)
|
||||
if err == nil && mute != nil {
|
||||
_ = CacheObject(tenantID, cacheNamespaceGroupMute, cacheID, groupMuteCacheValue{Found: true, Mute: mute})
|
||||
} else if errors.Is(err, types.ErrNotFound) {
|
||||
_ = CacheObject(tenantID, cacheNamespaceGroupMute, cacheID, groupMuteCacheValue{})
|
||||
}
|
||||
return mute, err
|
||||
}
|
||||
|
||||
func (groupBusinessMapper) UpsertMute(tenantID types.TenantID, mute *types.GroupMute) (*types.GroupMute, error) {
|
||||
@@ -2440,6 +2462,12 @@ func (groupBusinessMapper) UpsertMute(tenantID types.TenantID, mute *types.Group
|
||||
updated, err := tenantAdp.TenantGroupMuteUpsert(tenantID, mute)
|
||||
if err == nil {
|
||||
invalidateGroupMemberPages(tenantID, mute.Topic)
|
||||
cacheID := groupMemberCacheID(mute.Topic, mute.UserID)
|
||||
if updated != nil {
|
||||
_ = CacheObject(tenantID, cacheNamespaceGroupMute, cacheID, groupMuteCacheValue{Found: true, Mute: updated})
|
||||
} else {
|
||||
_ = DeleteCachedObject(tenantID, cacheNamespaceGroupMute, cacheID)
|
||||
}
|
||||
}
|
||||
return updated, err
|
||||
}
|
||||
@@ -2456,6 +2484,7 @@ func (groupBusinessMapper) DeleteMute(tenantID types.TenantID, topic string, uid
|
||||
return err
|
||||
}
|
||||
invalidateGroupMemberPages(tenantID, topic)
|
||||
_ = CacheObject(tenantID, cacheNamespaceGroupMute, groupMemberCacheID(topic, uid), groupMuteCacheValue{})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2581,11 +2610,32 @@ func (groupBusinessMapper) CanPost(tenantID types.TenantID, topic string, uid ty
|
||||
if strings.TrimSpace(topic) == "" || uid.IsZero() || now.IsZero() {
|
||||
return false, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if admin {
|
||||
return true, nil
|
||||
}
|
||||
topic = strings.TrimSpace(topic)
|
||||
mapper := groupBusinessMapper{}
|
||||
settings, err := mapper.GetSettings(tenantID, topic)
|
||||
if err != nil && !errors.Is(err, types.ErrNotFound) {
|
||||
return false, err
|
||||
}
|
||||
if settings != nil && settings.GroupStatus == types.GroupStatusActive && settings.MuteAll {
|
||||
return false, nil
|
||||
}
|
||||
mute, err := mapper.GetMute(tenantID, topic, uid)
|
||||
if errors.Is(err, types.ErrNotFound) {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return tenantAdp.TenantGroupCanPost(tenantID, strings.TrimSpace(topic), uid, admin, now)
|
||||
return mute == nil || mute.Status != types.GroupMuteStatusActive ||
|
||||
(mute.ExpiresAt != nil && !mute.ExpiresAt.After(now)), nil
|
||||
}
|
||||
|
||||
type groupMuteCacheValue struct {
|
||||
Found bool `json:"found"`
|
||||
Mute *types.GroupMute `json:"mute,omitempty"`
|
||||
}
|
||||
|
||||
func groupMemberCacheID(topic string, uid types.Uid) string {
|
||||
|
||||
Reference in New Issue
Block a user