fix(server): coalesce platform evict fanout
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tinode/chat/server/auth"
|
"github.com/tinode/chat/server/auth"
|
||||||
@@ -27,6 +28,29 @@ const (
|
|||||||
realtimeTargetUsers = "users"
|
realtimeTargetUsers = "users"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const platformEvictFanoutDebounce = 750 * time.Millisecond
|
||||||
|
|
||||||
|
type platformEvictFanoutKey struct {
|
||||||
|
tenantID types.TenantID
|
||||||
|
uid types.Uid
|
||||||
|
platf string
|
||||||
|
reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
type platformEvictFanoutPending struct {
|
||||||
|
skipSid string
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
var platformEvictFanout = struct {
|
||||||
|
sync.Mutex
|
||||||
|
pending map[platformEvictFanoutKey]*platformEvictFanoutPending
|
||||||
|
}{
|
||||||
|
pending: make(map[platformEvictFanoutKey]*platformEvictFanoutPending),
|
||||||
|
}
|
||||||
|
|
||||||
|
var publishRealtimeFanout = store.PublishRealtimeFanout
|
||||||
|
|
||||||
type realtimeGroupUnreadRecipient struct {
|
type realtimeGroupUnreadRecipient struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
@@ -69,7 +93,7 @@ func publishRealtimeTopicMessage(topic string, msg *ServerComMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
dedupKey := realtimeMessageDedupKey(topic, msg)
|
dedupKey := realtimeMessageDedupKey(topic, msg)
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), msg.TenantID, realtimeKindMessage, realtimeTargetTopic, topic, msg.SkipSid, dedupKey, msg); err != nil {
|
if err := publishRealtimeFanout(currentNodeID(), msg.TenantID, realtimeKindMessage, realtimeTargetTopic, topic, msg.SkipSid, dedupKey, msg); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindMessage, realtimeTargetTopic, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindMessage, realtimeTargetTopic, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish topic=%s failed: %v", topic, err)
|
logs.Warn.Printf("redis realtime fanout: publish topic=%s failed: %v", topic, err)
|
||||||
return
|
return
|
||||||
@@ -89,7 +113,7 @@ func publishRealtimeUserMessage(uid types.Uid, msg *ServerComMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
dedupKey := realtimeMessageDedupKey(uid.UserId(), msg)
|
dedupKey := realtimeMessageDedupKey(uid.UserId(), msg)
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), msg.TenantID, realtimeKindMessage, realtimeTargetUser, uid.UserId(), msg.SkipSid, dedupKey, msg); err != nil {
|
if err := publishRealtimeFanout(currentNodeID(), msg.TenantID, realtimeKindMessage, realtimeTargetUser, uid.UserId(), msg.SkipSid, dedupKey, msg); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindMessage, realtimeTargetUser, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindMessage, realtimeTargetUser, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish user=%s failed: %v", uid.UserId(), err)
|
logs.Warn.Printf("redis realtime fanout: publish user=%s failed: %v", uid.UserId(), err)
|
||||||
return
|
return
|
||||||
@@ -109,7 +133,7 @@ func publishRealtimeGroupUnread(tenantID types.TenantID, topic, actor string, se
|
|||||||
Recipients: recipients,
|
Recipients: recipients,
|
||||||
}
|
}
|
||||||
dedupKey := "group-unread:" + topic + ":" + strconv.FormatInt(seqID, 10)
|
dedupKey := "group-unread:" + topic + ":" + strconv.FormatInt(seqID, 10)
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), tenantID, realtimeKindGroupUnread,
|
if err := publishRealtimeFanout(currentNodeID(), tenantID, realtimeKindGroupUnread,
|
||||||
realtimeTargetUsers, topic, "", dedupKey, payload); err != nil {
|
realtimeTargetUsers, topic, "", dedupKey, payload); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindGroupUnread, realtimeTargetUsers, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindGroupUnread, realtimeTargetUsers, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish group unread topic=%s seq=%d failed: %v", topic, seqID, err)
|
logs.Warn.Printf("redis realtime fanout: publish group unread topic=%s seq=%d failed: %v", topic, seqID, err)
|
||||||
@@ -140,7 +164,7 @@ func publishRealtimeCallEvent(msg *ClientComMessage) {
|
|||||||
Timestamp: msg.Timestamp,
|
Timestamp: msg.Timestamp,
|
||||||
}
|
}
|
||||||
dedupKey := "call:" + msg.RcptTo + ":" + msg.AsUser + ":" + event.Event + ":" + strconv.FormatInt(msg.Note.SeqId, 10)
|
dedupKey := "call:" + msg.RcptTo + ":" + msg.AsUser + ":" + event.Event + ":" + strconv.FormatInt(msg.Note.SeqId, 10)
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), msg.TenantID, realtimeKindCallEvent, realtimeTargetTopic, msg.RcptTo, sid, dedupKey, event); err != nil {
|
if err := publishRealtimeFanout(currentNodeID(), msg.TenantID, realtimeKindCallEvent, realtimeTargetTopic, msg.RcptTo, sid, dedupKey, event); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindCallEvent, realtimeTargetTopic, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindCallEvent, realtimeTargetTopic, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish call event topic=%s from=%s event=%s seq=%d failed: %v",
|
logs.Warn.Printf("redis realtime fanout: publish call event topic=%s from=%s event=%s seq=%d failed: %v",
|
||||||
msg.RcptTo, msg.AsUser, event.Event, msg.Note.SeqId, err)
|
msg.RcptTo, msg.AsUser, event.Event, msg.Note.SeqId, err)
|
||||||
@@ -162,7 +186,7 @@ func publishRealtimeEvictUser(tenantID types.TenantID, uid types.Uid, reason str
|
|||||||
msg.Ctrl.Params = map[string]any{"reason": reason}
|
msg.Ctrl.Params = map[string]any{"reason": reason}
|
||||||
}
|
}
|
||||||
dedupKey := "evict:user:" + uid.UserId() + ":" + reason
|
dedupKey := "evict:user:" + uid.UserId() + ":" + reason
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), tenantID, realtimeKindSessionControl, realtimeTargetUser, uid.UserId(), skipSid, dedupKey, msg); err != nil {
|
if err := publishRealtimeFanout(currentNodeID(), tenantID, realtimeKindSessionControl, realtimeTargetUser, uid.UserId(), skipSid, dedupKey, msg); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetUser, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetUser, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish evict user=%s failed: %v", uid.UserId(), err)
|
logs.Warn.Printf("redis realtime fanout: publish evict user=%s failed: %v", uid.UserId(), err)
|
||||||
return
|
return
|
||||||
@@ -183,7 +207,7 @@ func publishRealtimeEvictDevice(tenantID types.TenantID, uid types.Uid, deviceID
|
|||||||
}
|
}
|
||||||
target := uid.UserId() + ":" + strings.TrimSpace(deviceID)
|
target := uid.UserId() + ":" + strings.TrimSpace(deviceID)
|
||||||
dedupKey := "evict:device:" + target + ":" + reason
|
dedupKey := "evict:device:" + target + ":" + reason
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), tenantID, realtimeKindSessionControl, realtimeTargetDevice, target, "", dedupKey, msg); err != nil {
|
if err := publishRealtimeFanout(currentNodeID(), tenantID, realtimeKindSessionControl, realtimeTargetDevice, target, "", dedupKey, msg); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetDevice, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetDevice, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish evict device=%s failed: %v", target, err)
|
logs.Warn.Printf("redis realtime fanout: publish evict device=%s failed: %v", target, err)
|
||||||
return
|
return
|
||||||
@@ -191,24 +215,52 @@ func publishRealtimeEvictDevice(tenantID types.TenantID, uid types.Uid, deviceID
|
|||||||
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetDevice, "ok")
|
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetDevice, "ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
// publishRealtimeEvictUserPlatform evicts all sessions of the user on the given
|
|
||||||
// platform across cluster nodes. skipSid (the new login session) is part of the
|
|
||||||
// dedup key because repeated logins on the same platform within the dedup window
|
|
||||||
// must each reach the subscriber nodes.
|
|
||||||
func publishRealtimeEvictUserPlatform(tenantID types.TenantID, uid types.Uid, platf, reason, skipSid string) {
|
func publishRealtimeEvictUserPlatform(tenantID types.TenantID, uid types.Uid, platf, reason, skipSid string) {
|
||||||
if !tenantID.IsValid() || uid.IsZero() || strings.TrimSpace(platf) == "" {
|
if !tenantID.IsValid() || uid.IsZero() || strings.TrimSpace(platf) == "" {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetPlatf, "skip_invalid")
|
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetPlatf, "skip_invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
key := platformEvictFanoutKey{
|
||||||
|
tenantID: tenantID,
|
||||||
|
uid: uid,
|
||||||
|
platf: strings.TrimSpace(platf),
|
||||||
|
reason: reason,
|
||||||
|
}
|
||||||
|
platformEvictFanout.Lock()
|
||||||
|
if pending := platformEvictFanout.pending[key]; pending != nil {
|
||||||
|
pending.skipSid = skipSid
|
||||||
|
pending.timer.Reset(platformEvictFanoutDebounce)
|
||||||
|
platformEvictFanout.Unlock()
|
||||||
|
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetPlatf, "coalesced")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pending := &platformEvictFanoutPending{skipSid: skipSid}
|
||||||
|
pending.timer = time.AfterFunc(platformEvictFanoutDebounce, func() {
|
||||||
|
platformEvictFanout.Lock()
|
||||||
|
current := platformEvictFanout.pending[key]
|
||||||
|
if current != pending {
|
||||||
|
platformEvictFanout.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(platformEvictFanout.pending, key)
|
||||||
|
finalSkipSid := pending.skipSid
|
||||||
|
platformEvictFanout.Unlock()
|
||||||
|
publishRealtimeEvictUserPlatformNow(key.tenantID, key.uid, key.platf, key.reason, finalSkipSid)
|
||||||
|
})
|
||||||
|
platformEvictFanout.pending[key] = pending
|
||||||
|
platformEvictFanout.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishRealtimeEvictUserPlatformNow(tenantID types.TenantID, uid types.Uid, platf, reason, skipSid string) {
|
||||||
msg := NoErrEvicted("", "", types.TimeNow())
|
msg := NoErrEvicted("", "", types.TimeNow())
|
||||||
msg.TenantID = tenantID
|
msg.TenantID = tenantID
|
||||||
msg.AsUser = uid.UserId()
|
msg.AsUser = uid.UserId()
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
msg.Ctrl.Params = map[string]any{"reason": reason}
|
msg.Ctrl.Params = map[string]any{"reason": reason}
|
||||||
}
|
}
|
||||||
target := uid.UserId() + ":" + strings.TrimSpace(platf)
|
target := uid.UserId() + ":" + platf
|
||||||
dedupKey := "evict:platform:" + target + ":" + skipSid
|
dedupKey := "evict:platform:" + target + ":" + skipSid
|
||||||
if err := store.PublishRealtimeFanout(currentNodeID(), tenantID, realtimeKindSessionControl, realtimeTargetPlatf, target, skipSid, dedupKey, msg); err != nil {
|
if err := publishRealtimeFanout(currentNodeID(), tenantID, realtimeKindSessionControl, realtimeTargetPlatf, target, skipSid, dedupKey, msg); err != nil {
|
||||||
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetPlatf, "error")
|
immetrics.IncRealtimeFanout("publish", realtimeKindSessionControl, realtimeTargetPlatf, "error")
|
||||||
logs.Warn.Printf("redis realtime fanout: publish evict platform=%s failed: %v", target, err)
|
logs.Warn.Printf("redis realtime fanout: publish evict platform=%s failed: %v", target, err)
|
||||||
return
|
return
|
||||||
@@ -472,28 +524,33 @@ func deliverRealtimeSessionControl(envelope *store.RealtimeFanoutEnvelope, msg *
|
|||||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_invalid_target")
|
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_invalid_target")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
globals.sessionStore.EvictUserDevice(types.ParseUserId(uidText), deviceID, fanoutEvictReason(msg))
|
recordRealtimeSessionControlEvict(envelope, globals.sessionStore.EvictUserDevice(types.ParseUserId(uidText), deviceID, fanoutEvictReason(msg)))
|
||||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "evicted")
|
|
||||||
case realtimeTargetPlatf:
|
case realtimeTargetPlatf:
|
||||||
uidText, platf, ok := strings.Cut(envelope.Target, ":")
|
uidText, platf, ok := strings.Cut(envelope.Target, ":")
|
||||||
if !ok {
|
if !ok {
|
||||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_invalid_target")
|
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_invalid_target")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
globals.sessionStore.EvictUserPlatform(types.ParseUserId(uidText), platf, envelope.SkipSid, fanoutEvictReason(msg))
|
recordRealtimeSessionControlEvict(envelope, globals.sessionStore.EvictUserPlatform(types.ParseUserId(uidText), platf, envelope.SkipSid, fanoutEvictReason(msg)))
|
||||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "evicted")
|
|
||||||
case realtimeTargetUser:
|
case realtimeTargetUser:
|
||||||
uid := types.ParseUserId(envelope.Target)
|
uid := types.ParseUserId(envelope.Target)
|
||||||
if uid.IsZero() {
|
if uid.IsZero() {
|
||||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_invalid_target")
|
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "skip_invalid_target")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
globals.sessionStore.EvictUser(uid, envelope.SkipSid, fanoutEvictReason(msg))
|
recordRealtimeSessionControlEvict(envelope, globals.sessionStore.EvictUser(uid, envelope.SkipSid, fanoutEvictReason(msg)))
|
||||||
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "evicted")
|
|
||||||
}
|
}
|
||||||
_ = msg
|
_ = msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recordRealtimeSessionControlEvict(envelope *store.RealtimeFanoutEnvelope, evicted int) {
|
||||||
|
if evicted > 0 {
|
||||||
|
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "evicted")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
immetrics.IncRealtimeFanout("deliver", envelope.Kind, envelope.TargetType, "no_local_session")
|
||||||
|
}
|
||||||
|
|
||||||
func recordRealtimeFanoutDeliverResult(kind, targetType string, delivered int) {
|
func recordRealtimeFanoutDeliverResult(kind, targetType string, delivered int) {
|
||||||
if delivered > 0 {
|
if delivered > 0 {
|
||||||
immetrics.IncRealtimeFanout("deliver", kind, targetType, "local_delivered")
|
immetrics.IncRealtimeFanout("deliver", kind, targetType, "local_delivered")
|
||||||
|
|||||||
@@ -260,6 +260,61 @@ func TestRealtimeSessionControlStaleEvictDropped(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRealtimePlatformEvictFanoutCoalescesToLatestSession(t *testing.T) {
|
||||||
|
oldPublish := publishRealtimeFanout
|
||||||
|
t.Cleanup(func() {
|
||||||
|
publishRealtimeFanout = oldPublish
|
||||||
|
resetPlatformEvictFanoutForTest()
|
||||||
|
})
|
||||||
|
resetPlatformEvictFanoutForTest()
|
||||||
|
|
||||||
|
type call struct {
|
||||||
|
target string
|
||||||
|
skipSid string
|
||||||
|
dedupKey string
|
||||||
|
}
|
||||||
|
calls := make(chan call, 3)
|
||||||
|
publishRealtimeFanout = func(_ string, _ types.TenantID, kind, targetType, target, skipSid, dedupKey string, _ any) error {
|
||||||
|
if kind == realtimeKindSessionControl && targetType == realtimeTargetPlatf {
|
||||||
|
calls <- call{target: target, skipSid: skipSid, dedupKey: dedupKey}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantID := types.TenantID(1)
|
||||||
|
uid := types.Uid(1001)
|
||||||
|
publishRealtimeEvictUserPlatform(tenantID, uid, "web", "replaced_by_new_login", "sid-old")
|
||||||
|
publishRealtimeEvictUserPlatform(tenantID, uid, "web", "replaced_by_new_login", "sid-new")
|
||||||
|
publishRealtimeEvictUserPlatform(tenantID, uid, "web", "replaced_by_new_login", "sid-final")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case got := <-calls:
|
||||||
|
t.Fatalf("evict fanout published before debounce: %#v", got)
|
||||||
|
case <-time.After(platformEvictFanoutDebounce / 2):
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case got := <-calls:
|
||||||
|
if got.target != uid.UserId()+":web" {
|
||||||
|
t.Fatalf("target = %q", got.target)
|
||||||
|
}
|
||||||
|
if got.skipSid != "sid-final" {
|
||||||
|
t.Fatalf("skipSid = %q, want latest", got.skipSid)
|
||||||
|
}
|
||||||
|
if got.dedupKey != "evict:platform:"+uid.UserId()+":web:sid-final" {
|
||||||
|
t.Fatalf("dedupKey = %q", got.dedupKey)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * platformEvictFanoutDebounce):
|
||||||
|
t.Fatal("timed out waiting for coalesced evict fanout")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case got := <-calls:
|
||||||
|
t.Fatalf("expected one coalesced fanout, got extra %#v", got)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRealtimeP2PTopicFallbackRewritesVisibleInfoTopic(t *testing.T) {
|
func TestRealtimeP2PTopicFallbackRewritesVisibleInfoTopic(t *testing.T) {
|
||||||
oldStore := globals.sessionStore
|
oldStore := globals.sessionStore
|
||||||
oldHub := globals.hub
|
oldHub := globals.hub
|
||||||
@@ -314,6 +369,17 @@ func TestRealtimeP2PTopicFallbackRewritesVisibleInfoTopic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetPlatformEvictFanoutForTest() {
|
||||||
|
platformEvictFanout.Lock()
|
||||||
|
defer platformEvictFanout.Unlock()
|
||||||
|
for key, pending := range platformEvictFanout.pending {
|
||||||
|
if pending.timer != nil {
|
||||||
|
pending.timer.Stop()
|
||||||
|
}
|
||||||
|
delete(platformEvictFanout.pending, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRealtimeCallEventRoutesToLocalTopic(t *testing.T) {
|
func TestRealtimeCallEventRoutesToLocalTopic(t *testing.T) {
|
||||||
oldHub := globals.hub
|
oldHub := globals.hub
|
||||||
defer func() { globals.hub = oldHub }()
|
defer func() { globals.hub = oldHub }()
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ func (ss *SessionStore) Shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EvictUser terminates all sessions of a given user.
|
// EvictUser terminates all sessions of a given user.
|
||||||
func (ss *SessionStore) EvictUser(uid types.Uid, skipSid string, reason ...string) {
|
func (ss *SessionStore) EvictUser(uid types.Uid, skipSid string, reason ...string) int {
|
||||||
ss.lock.Lock()
|
ss.lock.Lock()
|
||||||
defer ss.lock.Unlock()
|
defer ss.lock.Unlock()
|
||||||
|
|
||||||
@@ -348,6 +348,7 @@ func (ss *SessionStore) EvictUser(uid types.Uid, skipSid string, reason ...strin
|
|||||||
if len(reason) > 0 && reason[0] != "" {
|
if len(reason) > 0 && reason[0] != "" {
|
||||||
evicted.Ctrl.Params = map[string]any{"reason": reason[0]}
|
evicted.Ctrl.Params = map[string]any{"reason": reason[0]}
|
||||||
}
|
}
|
||||||
|
count := 0
|
||||||
for _, s := range ss.sessCache {
|
for _, s := range ss.sessCache {
|
||||||
if s.uid == uid && !s.isMultiplex() && s.sid != skipSid {
|
if s.uid == uid && !s.isMultiplex() && s.sid != skipSid {
|
||||||
_, data := s.serialize(evicted)
|
_, data := s.serialize(evicted)
|
||||||
@@ -356,10 +357,12 @@ func (ss *SessionStore) EvictUser(uid types.Uid, skipSid string, reason ...strin
|
|||||||
if s.proto == LPOLL {
|
if s.proto == LPOLL {
|
||||||
ss.lru.Remove(s.lpTracker)
|
ss.lru.Remove(s.lpTracker)
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statsSet("LiveSessions", int64(len(ss.sessCache)))
|
statsSet("LiveSessions", int64(len(ss.sessCache)))
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvictUserDevice terminates all sessions of a given user connected with a device.
|
// EvictUserDevice terminates all sessions of a given user connected with a device.
|
||||||
|
|||||||
Reference in New Issue
Block a user