fix: protect publish path from mysql exhaustion
This commit is contained in:
@@ -17,6 +17,7 @@ type testGroupMessageRuntime struct {
|
||||
shortStates map[string]bool
|
||||
shortValues map[string]string
|
||||
objects map[string][]byte
|
||||
sets map[string]map[string]bool
|
||||
deletedPrefixes []string
|
||||
deletedObjects []string
|
||||
}
|
||||
@@ -28,6 +29,7 @@ func newTestGroupMessageRuntime() *testGroupMessageRuntime {
|
||||
shortStates: make(map[string]bool),
|
||||
shortValues: make(map[string]string),
|
||||
objects: make(map[string][]byte),
|
||||
sets: make(map[string]map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,16 +188,24 @@ func (r *testGroupMessageRuntime) DeleteCachedObjectsByPrefix(tenantID types.Ten
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testGroupMessageRuntime) AddCachedSetMember(types.TenantID, string, string, string) error {
|
||||
func (r *testGroupMessageRuntime) AddCachedSetMember(tenantID types.TenantID, namespace, id, member string) error {
|
||||
key := strconv.FormatInt(int64(tenantID), 10) + ":" + namespace + ":" + id
|
||||
if r.sets[key] == nil {
|
||||
r.sets[key] = make(map[string]bool)
|
||||
}
|
||||
r.sets[key][member] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testGroupMessageRuntime) RemoveCachedSetMember(types.TenantID, string, string, string) error {
|
||||
func (r *testGroupMessageRuntime) RemoveCachedSetMember(tenantID types.TenantID, namespace, id, member string) error {
|
||||
key := strconv.FormatInt(int64(tenantID), 10) + ":" + namespace + ":" + id
|
||||
delete(r.sets[key], member)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *testGroupMessageRuntime) CachedSetContains(types.TenantID, string, string, string) (bool, error) {
|
||||
return false, nil
|
||||
func (r *testGroupMessageRuntime) CachedSetContains(tenantID types.TenantID, namespace, id, member string) (bool, error) {
|
||||
key := strconv.FormatInt(int64(tenantID), 10) + ":" + namespace + ":" + id
|
||||
return r.sets[key][member], nil
|
||||
}
|
||||
|
||||
func (r *testGroupMessageRuntime) AllowRate(types.TenantID, string, int, time.Duration) (bool, int, time.Duration, error) {
|
||||
@@ -355,6 +365,29 @@ func TestMessageRuntimeSeqAndClientMidIdempotency(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanSendP2PUsesWarmRelationshipCache(t *testing.T) {
|
||||
oldRuntime := groupMsgRuntime
|
||||
runtime := newTestGroupMessageRuntime()
|
||||
groupMsgRuntime = runtime
|
||||
t.Cleanup(func() { groupMsgRuntime = oldRuntime })
|
||||
|
||||
tenantID := types.TenantID(7)
|
||||
userID := types.Uid(42)
|
||||
peerID := types.Uid(43)
|
||||
cacheFriendPair(tenantID, userID, peerID)
|
||||
|
||||
allowed, relationship, err := (relationsMapper{}).CanSendP2P(tenantID, userID, peerID)
|
||||
if err != nil || !allowed || relationship != types.RelationshipFriend {
|
||||
t.Fatalf("warm friend cache was not used: allowed=%v relationship=%q err=%v", allowed, relationship, err)
|
||||
}
|
||||
|
||||
cacheBlockOneWay(tenantID, peerID, userID)
|
||||
allowed, relationship, err = (relationsMapper{}).CanSendP2P(tenantID, userID, peerID)
|
||||
if err != nil || allowed || relationship != types.RelationshipBlockedByPeer {
|
||||
t.Fatalf("reverse block cache was not enforced: allowed=%v relationship=%q err=%v", allowed, relationship, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpMessageSeqFloorNeverMovesBackward(t *testing.T) {
|
||||
oldRuntime := groupMsgRuntime
|
||||
runtime := newTestGroupMessageRuntime()
|
||||
|
||||
@@ -3147,6 +3147,29 @@ func (relationsMapper) GetRelationshipContext(tenantID types.TenantID, userID, p
|
||||
}
|
||||
|
||||
func (relationsMapper) CanSendP2P(tenantID types.TenantID, userID, peerID types.Uid) (bool, string, error) {
|
||||
if userID.IsZero() || peerID.IsZero() || userID == peerID {
|
||||
return false, "", types.ErrMalformed
|
||||
}
|
||||
// A warm relationship cache is sufficient for the publish hot path. Block
|
||||
// entries are checked first in both directions; friendship and block writes
|
||||
// keep these sets coherent. Redis errors and cache misses fall back to the
|
||||
// authoritative database path below.
|
||||
blockCacheReady := true
|
||||
if blocked, err := cachedBlockContains(tenantID, userID, peerID); err != nil {
|
||||
blockCacheReady = false
|
||||
} else if blocked {
|
||||
return false, types.RelationshipBlockedByMe, nil
|
||||
}
|
||||
if blocked, err := cachedBlockContains(tenantID, peerID, userID); err != nil {
|
||||
blockCacheReady = false
|
||||
} else if blocked {
|
||||
return false, types.RelationshipBlockedByPeer, nil
|
||||
}
|
||||
if blockCacheReady {
|
||||
if friend, err := cachedFriendContains(tenantID, userID, peerID); err == nil && friend {
|
||||
return true, types.RelationshipFriend, nil
|
||||
}
|
||||
}
|
||||
ctx, err := (relationsMapper{}).GetRelationshipContext(tenantID, userID, peerID, "")
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
@@ -3168,6 +3191,9 @@ func (relationsMapper) BlockUser(tenantID types.TenantID, blockerID, blockedID t
|
||||
block, err := tenantAdp.TenantUserBlockUpsert(tenantID, blockerID, blockedID, now)
|
||||
if err == nil {
|
||||
cacheBlockOneWay(tenantID, blockerID, blockedID)
|
||||
// A positive friend cache must never outlive a new block. Removing both
|
||||
// directions also makes a later block-cache eviction fall back to MySQL.
|
||||
uncacheFriendPair(tenantID, blockerID, blockedID)
|
||||
}
|
||||
return block, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user