fix cluster proxy cleanup routing

This commit is contained in:
richjohnrambo
2026-09-24 10:18:07 +08:00
parent 9893452817
commit 75216c311d
3 changed files with 105 additions and 11 deletions
+30 -9
View File
@@ -36,6 +36,8 @@ const (
clusterRpcCompletionBuffer = 64 clusterRpcCompletionBuffer = 64
) )
var errClusterTopicOwnerUnavailable = errors.New("cluster topic owner unavailable")
// ProxyReqType is the type of proxy requests. // ProxyReqType is the type of proxy requests.
type ProxyReqType int type ProxyReqType int
@@ -926,9 +928,15 @@ func (c *Cluster) routeToTopicMaster(reqType ProxyReqType, msg *ClientComMessage
req := c.makeClusterReq(reqType, msg, topic, sess) req := c.makeClusterReq(reqType, msg, topic, sess)
// Find the cluster node which owns the topic, then forward to it. // Find the cluster node which owns the topic, then forward to it.
n := c.nodeForTopic(types.TopicKey{TenantID: req.TenantID, Topic: topic}) key := types.TopicKey{TenantID: req.TenantID, Topic: topic}
if reqType == ProxyReqLeave && !c.isRemoteTopic(key) {
// The proxy topic is stale after rehash/failover. The local session has already been
// detached, and there is no remote master to notify anymore.
return errClusterTopicOwnerUnavailable
}
n := c.nodeForTopic(key)
if n == nil { if n == nil {
return errors.New("node for topic not found") return errClusterTopicOwnerUnavailable
} }
return n.proxyToMasterAsync(req) return n.proxyToMasterAsync(req)
} }
@@ -967,9 +975,14 @@ func (c *Cluster) topicProxyGone(tenantID types.TenantID, topicName string) erro
} }
// Find the cluster node which owns the topic, then forward to it. // Find the cluster node which owns the topic, then forward to it.
n := c.nodeForTopic(types.TopicKey{TenantID: tenantID, Topic: topicName}) key := types.TopicKey{TenantID: tenantID, Topic: topicName}
if !c.isRemoteTopic(key) {
// The proxy topic is stale after rehash/failover. Nothing remote needs to be cleaned.
return errClusterTopicOwnerUnavailable
}
n := c.nodeForTopic(key)
if n == nil { if n == nil {
return errors.New("node for topic not found") return errClusterTopicOwnerUnavailable
} }
req := c.makeClusterReq(ProxyReqLeave, nil, topicName, &Session{tenantID: tenantID}) req := c.makeClusterReq(ProxyReqLeave, nil, topicName, &Session{tenantID: tenantID})
@@ -1015,11 +1028,7 @@ func clusterInit(configString json.RawMessage, self *string) int {
return 1 return 1
} }
gob.Register([]any{}) registerClusterGobTypes()
gob.Register(map[string]any{})
gob.Register(map[string]int{})
gob.Register(map[string]string{})
gob.Register(MsgAccessMode{})
if config.NumProxyEventGoRoutines != 0 { if config.NumProxyEventGoRoutines != 0 {
logs.Warn.Println("Cluster config: field num_proxy_event_goroutines is deprecated.") logs.Warn.Println("Cluster config: field num_proxy_event_goroutines is deprecated.")
@@ -1072,6 +1081,18 @@ func clusterInit(configString json.RawMessage, self *string) int {
return workerId return workerId
} }
func registerClusterGobTypes() {
gob.Register([]any{})
gob.Register(map[string]any{})
gob.Register(map[string]int{})
gob.Register(map[string]string{})
gob.Register(MsgAccessMode{})
gob.Register(imSystemMessageActor{})
gob.Register([]imSystemMessageActor{})
gob.Register(imSystemMessageAction{})
gob.Register([]imSystemMessageAction{})
}
// Proxied session is being closed at the Master node. // Proxied session is being closed at the Master node.
func (sess *Session) closeRPC() { func (sess *Session) closeRPC() {
if sess.isMultiplex() { if sess.isMultiplex() {
+72
View File
@@ -0,0 +1,72 @@
package main
import (
"bytes"
"encoding/gob"
"errors"
"testing"
"time"
"github.com/tinode/chat/server/store/types"
)
func TestClusterGobRegistersSystemMessagePayloadTypes(t *testing.T) {
registerClusterGobTypes()
msg := &ClusterRoute{
SrvMsg: &ServerComMessage{
TenantID: types.TenantID(10001),
RcptTo: "grpTest",
Data: &MsgServerData{
Topic: "grpTest",
Timestamp: time.Now().UTC().Round(time.Millisecond),
SeqId: 1,
Head: map[string]any{
"x-im-type": "system_action",
"x-im-template": "group_member_joined",
},
Content: map[string]any{
"text": "成员加入了群聊",
"template": "group_member_joined",
"actors": []imSystemMessageActor{{
UserID: "usr123",
Click: map[string]any{"type": "user_profile", "target_id": "usr123"},
}},
"actions": []imSystemMessageAction{{
Type: "group_profile",
Label: "查看群聊",
TargetID: "grpTest",
}},
},
},
},
}
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(msg); err != nil {
t.Fatalf("encode cluster route with system message payload: %v", err)
}
var decoded ClusterRoute
if err := gob.NewDecoder(&buf).Decode(&decoded); err != nil {
t.Fatalf("decode cluster route with system message payload: %v", err)
}
}
func TestClusterRouteLeaveIgnoresStaleLocalProxyOwner(t *testing.T) {
cluster := &Cluster{
thisNodeName: "node-a",
nodes: map[string]*ClusterNode{},
}
cluster.rehash([]string{"node-a"})
err := cluster.routeToTopicMaster(ProxyReqLeave, nil, "grpStaleProxy", &Session{tenantID: types.TenantID(10001)})
if !errors.Is(err, errClusterTopicOwnerUnavailable) {
t.Fatalf("expected stale proxy leave to return owner unavailable, got %v", err)
}
err = cluster.topicProxyGone(types.TenantID(10001), "grpStaleProxy")
if !errors.Is(err, errClusterTopicOwnerUnavailable) {
t.Fatalf("expected stale proxy shutdown to return owner unavailable, got %v", err)
}
}
+3 -2
View File
@@ -7,6 +7,7 @@
package main package main
import ( import (
"errors"
"net/http" "net/http"
"time" "time"
@@ -101,7 +102,7 @@ func (t *Topic) runProxy(hub *Hub) {
s.detachSession(t.name) s.detachSession(t.name)
} }
if err := globals.cluster.topicProxyGone(t.tenantID, t.name); err != nil { if err := globals.cluster.topicProxyGone(t.tenantID, t.name); err != nil && !errors.Is(err, errClusterTopicOwnerUnavailable) {
logs.Warn.Printf("proxy topic[%s] shutdown: failed to notify master - %s", t.name, err) logs.Warn.Printf("proxy topic[%s] shutdown: failed to notify master - %s", t.name, err)
} }
@@ -164,7 +165,7 @@ func (t *Topic) handleProxyLeaveRequest(msg *ClientComMessage, killTimer *time.T
} }
} }
if err := globals.cluster.routeToTopicMaster(ProxyReqLeave, msg, t.name, msg.sess); err != nil { if err := globals.cluster.routeToTopicMaster(ProxyReqLeave, msg, t.name, msg.sess); err != nil && !errors.Is(err, errClusterTopicOwnerUnavailable) {
logs.Warn.Printf("proxy topic[%s]: route leave request from proxy to master failed - %s", t.name, err) logs.Warn.Printf("proxy topic[%s]: route leave request from proxy to master failed - %s", t.name, err)
} }
if len(t.sessions) == 0 { if len(t.sessions) == 0 {