fix p2p permission refresh during load tests

This commit is contained in:
richjohnrambo
2026-09-27 12:53:09 +08:00
parent a57d1e8d4a
commit f9f7321b11
9 changed files with 269 additions and 15 deletions
+1
View File
@@ -2367,6 +2367,7 @@ func serveIMP2PConversation(wrt http.ResponseWriter, req *http.Request) {
writeIMRelationError(wrt, req, err)
return
}
publishIMP2PPermissionsRefresh(principal.TenantID, topic, principal.Uid, peer)
ctx, err := store.Relations.GetRelationshipContext(principal.TenantID, principal.Uid, peer, topic)
if err != nil {
writeIMRelationError(wrt, req, err)
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"encoding/json"
"strings"
"time"
"github.com/tinode/chat/server/logs"
"github.com/tinode/chat/server/store/types"
)
const p2pPermissionsRefreshedEvent = "p2p.permissions.refreshed"
type p2pPermissionsPayload struct {
UserIDs []string `json:"user_ids"`
}
// publishIMP2PPermissionsRefresh asks the topic master to reload permissions
// after the HTTP API has repaired or created the two P2P subscriptions.
func publishIMP2PPermissionsRefresh(tenantID types.TenantID, topic string, uids ...types.Uid) {
topic = strings.TrimSpace(topic)
if !tenantID.IsValid() || topic == "" {
return
}
payload := p2pPermissionsPayload{UserIDs: make([]string, 0, len(uids))}
for _, uid := range uids {
if !uid.IsZero() {
payload.UserIDs = append(payload.UserIDs, uid.UserId())
}
}
if len(payload.UserIDs) == 0 {
return
}
encoded, err := json.Marshal(payload)
if err != nil {
logs.Warn.Printf("p2p permissions refresh: encode topic=%s: %v", topic, err)
return
}
message := &ServerComMessage{
TenantID: tenantID,
RcptTo: topic,
Timestamp: time.Now().UTC().Round(time.Millisecond),
Info: &MsgServerInfo{
Topic: topic, What: "p2p", Event: p2pPermissionsRefreshedEvent,
Payload: json.RawMessage(encoded),
},
}
if globals.hub != nil {
select {
case globals.hub.routeSrv <- message:
default:
logs.Warn.Printf("p2p permissions refresh: hub queue full topic=%s", topic)
}
}
publishRealtimeTopicMessage(topic, message)
}
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"encoding/json"
"testing"
"github.com/golang/mock/gomock"
"github.com/tinode/chat/server/store/types"
)
func TestHandleP2PPermissionsRefreshReloadsLoadedTopic(t *testing.T) {
helper := TopicTestHelper{}
helper.setUp(t, 2, types.TopicCatP2P, "p2p-test", true)
defer helper.tearDown()
uid := helper.uids[0]
stale := types.ModeCP2P &^ types.ModeWrite
pud := helper.topic.perUser[uid]
pud.modeWant = stale
pud.modeGiven = stale
helper.topic.perUser[uid] = pud
helper.ss.EXPECT().Get(types.TenantID(1), "p2p-test", uid, true).Return(&types.Subscription{
TenantID: 1, User: uid.String(), Topic: "p2p-test",
ModeWant: types.ModeCP2P, ModeGiven: types.ModeCP2P,
}, nil)
payload, err := json.Marshal(p2pPermissionsPayload{UserIDs: []string{uid.UserId()}})
if err != nil {
t.Fatal(err)
}
helper.topic.handleServerMsg(&ServerComMessage{
TenantID: 1,
Info: &MsgServerInfo{What: "p2p", Event: p2pPermissionsRefreshedEvent, Payload: payload},
})
helper.finish()
got := helper.topic.perUser[uid]
if !got.modeWant.IsWriter() || !got.modeGiven.IsWriter() {
t.Fatalf("permissions were not refreshed: want=%s given=%s", got.modeWant, got.modeGiven)
}
}
func TestP2PPublishReloadsStalePermissions(t *testing.T) {
helper := TopicTestHelper{}
helper.setUp(t, 2, types.TopicCatP2P, "p2p-test", true)
defer helper.tearDown()
uid := helper.uids[0]
stale := types.ModeCP2P &^ types.ModeWrite
pud := helper.topic.perUser[uid]
pud.modeWant = stale
pud.modeGiven = stale
helper.topic.perUser[uid] = pud
helper.ss.EXPECT().Get(types.TenantID(1), "p2p-test", uid, true).Return(&types.Subscription{
TenantID: 1, User: uid.String(), Topic: "p2p-test",
ModeWant: types.ModeCP2P, ModeGiven: types.ModeCP2P,
}, nil)
helper.mm.EXPECT().Save(types.TenantID(1), gomock.Any(), gomock.Any(), gomock.Any()).
Return(&types.MessageSaveResult{SeqId: 1, MarkedReadBySender: true}, nil)
err := helper.topic.saveAndBroadcastMessage(&ClientComMessage{
Id: "pub-1", Original: "p2p-test", Timestamp: types.TimeNow(), sess: helper.sessions[0],
}, uid, true, nil, nil, "test")
helper.finish()
if err != nil {
t.Fatalf("publish failed after permission reload: %v", err)
}
}
+30 -1
View File
@@ -678,6 +678,10 @@ func (t *Topic) handleServerMsg(msg *ServerComMessage) {
// Ignore message - the topic is paused or being deleted.
return
}
if msg.Info != nil && msg.Info.What == "p2p" && msg.Info.Event == p2pPermissionsRefreshedEvent {
t.handleP2PPermissionsRefresh(msg.Info)
return
}
if msg.Pres != nil {
t.handlePresence(msg)
} else if msg.Info != nil || msg.Data != nil {
@@ -700,6 +704,24 @@ func (t *Topic) handleServerMsg(msg *ServerComMessage) {
}
}
func (t *Topic) handleP2PPermissionsRefresh(info *MsgServerInfo) {
if t.cat != types.TopicCatP2P || t.isProxy {
return
}
var payload p2pPermissionsPayload
if err := json.Unmarshal(info.Payload, &payload); err != nil {
logs.Warn.Printf("topic[%s]: failed to decode p2p permissions refresh: %v", t.name, err)
return
}
uids := make([]types.Uid, 0, len(payload.UserIDs))
for _, raw := range payload.UserIDs {
if uid := types.ParseUserId(raw); !uid.IsZero() {
uids = append(uids, uid)
}
}
t.refreshPerUserFromDB(types.ZeroUid, uids...)
}
// flexUID accepts a JSON string or number representation of a user id: the
// HTTP interface publishes base32 user id strings, while Kafka admin commands
// publish numeric database ids.
@@ -1282,8 +1304,15 @@ func (t *Topic) saveAndBroadcastMessage(msg *ClientComMessage, asUid types.Uid,
return types.ErrPermissionDenied
}
} else {
// EnsureP2P may have repaired the subscription in the database while this
// topic was already loaded. Reload once on denied P2P publishes so a lost
// or delayed refresh event cannot leave the conversation permanently stale.
if t.cat == types.TopicCatP2P && userFound && !(pud.modeWant & pud.modeGiven).IsWriter() && store.Subs != nil {
t.refreshPerUserFromDB(asUid, asUid)
pud, userFound = t.perUser[asUid]
}
// If it's not 'sys' check write permission.
if !(pud.modeWant & pud.modeGiven).IsWriter() {
if !userFound || !(pud.modeWant & pud.modeGiven).IsWriter() {
msg.sess.queueOut(ErrPermissionDenied(msg.Id, t.original(asUid), msg.Timestamp))
return types.ErrPermissionDenied
}
+4
View File
@@ -818,6 +818,10 @@ func TestHandleBroadcastDataMissingWritePermission(t *testing.T) {
pud := helper.topic.perUser[uid1]
pud.modeGiven = types.ModeRead | types.ModeJoin
helper.topic.perUser[uid1] = pud
helper.ss.EXPECT().Get(types.TenantID(1), topicName, uid1, true).Return(&types.Subscription{
TenantID: 1, User: uid1.String(), Topic: topicName,
ModeWant: pud.modeWant, ModeGiven: pud.modeGiven,
}, nil)
// Make test message.
from := helper.uids[0].UserId()