fix: broadcast offline presence as soon as the last session drops

Logout left contacts seeing the user online until the me topic idle timeout
fired presUsersOfInterest(off), and even that frame was silently dropped
for p2p routes by handlePresence's xoriginal check. MarkUserOffline now
returns the remaining session count and cleanUp notifies users of interest
immediately once it hits zero; p2p presence is delivered straight to the
contact's me topic, mirroring presSingleUserOffline. Zentao #22.
This commit is contained in:
Austin
2026-09-18 17:42:18 +08:00
parent f4cedc7d75
commit 753a04ed12
4 changed files with 53 additions and 11 deletions
+8 -8
View File
@@ -109,7 +109,7 @@ type groupMessageRuntime interface {
InvalidateHotMessages(tenantID types.TenantID, topic string, ranges []types.Range) error
MarkUserOnline(tenantID types.TenantID, uid types.Uid, sid, nodeID, device, userAgent string) error
RefreshUserPresence(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error
MarkUserOffline(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error
MarkUserOffline(tenantID types.TenantID, uid types.Uid, sid, nodeID string) (int, error)
GetUserPresence(tenantID types.TenantID, uid types.Uid) (*UserPresence, error)
MarkTopicMemberOnline(tenantID types.TenantID, topic string, uid types.Uid, nodeID string) error
MarkTopicMemberOffline(tenantID types.TenantID, topic string, uid types.Uid, nodeID string) error
@@ -300,9 +300,9 @@ func RefreshUserPresence(tenantID types.TenantID, uid types.Uid, sid, nodeID str
return groupMsgRuntime.RefreshUserPresence(tenantID, uid, sid, nodeID)
}
func MarkUserOffline(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error {
func MarkUserOffline(tenantID types.TenantID, uid types.Uid, sid, nodeID string) (int, error) {
if groupMsgRuntime == nil || !tenantID.IsValid() || uid.IsZero() || sid == "" {
return nil
return 0, nil
}
return groupMsgRuntime.MarkUserOffline(tenantID, uid, sid, nodeID)
}
@@ -939,7 +939,7 @@ func (r *redisGroupMessageRuntime) RefreshUserPresence(tenantID types.TenantID,
return err
}
func (r *redisGroupMessageRuntime) MarkUserOffline(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error {
func (r *redisGroupMessageRuntime) MarkUserOffline(tenantID types.TenantID, uid types.Uid, sid, nodeID string) (int, error) {
if nodeID == "" {
nodeID = "local"
}
@@ -956,11 +956,11 @@ func (r *redisGroupMessageRuntime) MarkUserOffline(tenantID types.TenantID, uid
pipe.SRem(ctx, sessionsKey, member)
pipe.SRem(ctx, nodeKey, uid.UserId())
if _, err := pipe.Exec(ctx); err != nil {
return err
return 0, err
}
count, err := r.client.SCard(ctx, sessionsKey).Result()
if err != nil {
return err
return 0, err
}
if count > 0 {
pipe = r.client.Pipeline()
@@ -968,14 +968,14 @@ func (r *redisGroupMessageRuntime) MarkUserOffline(tenantID types.TenantID, uid
pipe.Expire(ctx, userKey, ttl)
pipe.Expire(ctx, sessionsKey, ttl)
_, err = pipe.Exec(ctx)
return err
return int(count), err
}
pipe = r.client.Pipeline()
pipe.HSet(ctx, userKey, "online", "0", "session_count", 0, "node_id", nodeID, "last_seen_at", now.Format(time.RFC3339Nano))
pipe.Expire(ctx, userKey, ttl)
pipe.Del(ctx, sessionsKey)
_, err = pipe.Exec(ctx)
return err
return 0, err
}
func (r *redisGroupMessageRuntime) GetUserPresence(tenantID types.TenantID, uid types.Uid) (*UserPresence, error) {
+2 -2
View File
@@ -114,8 +114,8 @@ func (r *testGroupMessageRuntime) RefreshUserPresence(types.TenantID, types.Uid,
return nil
}
func (r *testGroupMessageRuntime) MarkUserOffline(types.TenantID, types.Uid, string, string) error {
return nil
func (r *testGroupMessageRuntime) MarkUserOffline(types.TenantID, types.Uid, string, string) (int, error) {
return 0, nil
}
func (r *testGroupMessageRuntime) GetUserPresence(types.TenantID, types.Uid) (*UserPresence, error) {