fix: sync scoped connection and outbox exclusion updates
This commit is contained in:
parent
aa21bd04e1
commit
cbccd6a8d9
58 changed files with 919 additions and 1435 deletions
|
|
@ -77,12 +77,7 @@ func (r *Router) onAuthBindTempAuthKey(ctx context.Context, req *tg.AuthBindTemp
|
|||
r.tempKeyResolveCache.Delete(id)
|
||||
}
|
||||
if r.deps.Sessions != nil {
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
rawAuthKeyID, _ := RawAuthKeyIDFrom(ctx)
|
||||
scoped.BindAuthKeyForSession(rawAuthKeyID, sessionID, authKeyIDFromInt64(req.PermAuthKeyID))
|
||||
} else {
|
||||
r.deps.Sessions.BindAuthKey(sessionID, authKeyIDFromInt64(req.PermAuthKeyID))
|
||||
}
|
||||
r.deps.Sessions.BindAuthKeyForSession(id, sessionID, authKeyIDFromInt64(req.PermAuthKeyID))
|
||||
}
|
||||
r.invalidateAuthUserCache(id)
|
||||
return true, nil
|
||||
|
|
@ -192,14 +187,8 @@ func (r *Router) bindLoginTokenTarget(target loginTokenTarget, userID int64) {
|
|||
if r.deps.Sessions == nil || target.sessionID == 0 {
|
||||
return
|
||||
}
|
||||
if scoped, ok := r.scopedSessions(); ok && target.rawAuthKeyID != ([8]byte{}) {
|
||||
scoped.BindAuthKeyForSession(target.rawAuthKeyID, target.sessionID, target.authKeyID)
|
||||
scoped.BindUserForAuthKey(target.rawAuthKeyID, target.sessionID, userID)
|
||||
r.announceSessionOnline(loginTokenTargetContext(target, userID), userID)
|
||||
return
|
||||
}
|
||||
r.deps.Sessions.BindAuthKey(target.sessionID, target.authKeyID)
|
||||
r.deps.Sessions.BindUser(target.sessionID, userID)
|
||||
r.deps.Sessions.BindAuthKeyForSession(target.rawAuthKeyID, target.sessionID, target.authKeyID)
|
||||
r.deps.Sessions.BindUserForAuthKey(target.rawAuthKeyID, target.sessionID, userID)
|
||||
r.announceSessionOnline(loginTokenTargetContext(target, userID), userID)
|
||||
}
|
||||
|
||||
|
|
@ -220,19 +209,13 @@ func (r *Router) pushLoginTokenAccepted(ctx context.Context, target loginTokenTa
|
|||
Update: &tg.UpdateLoginToken{},
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
}
|
||||
if immediate, ok := r.deps.Sessions.(ScopedImmediateSessionPusher); ok && target.rawAuthKeyID != ([8]byte{}) {
|
||||
if immediate, ok := r.deps.Sessions.(ImmediateSessionPusher); ok {
|
||||
if err := immediate.PushToSessionForAuthKeyImmediate(ctx, target.rawAuthKeyID, target.sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
r.log.Debug("push login token accepted immediate", zap.Int64("session_id", target.sessionID), zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
if scoped, ok := r.scopedSessions(); ok && target.rawAuthKeyID != ([8]byte{}) {
|
||||
if err := scoped.PushToSessionForAuthKey(ctx, target.rawAuthKeyID, target.sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
r.log.Debug("push login token accepted", zap.Int64("session_id", target.sessionID), zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := r.deps.Sessions.PushToSession(ctx, target.sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
if err := r.deps.Sessions.PushToSessionForAuthKey(ctx, target.rawAuthKeyID, target.sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
r.log.Debug("push login token accepted", zap.Int64("session_id", target.sessionID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
|
@ -713,13 +696,7 @@ func (r *Router) bindSessionUser(ctx context.Context, userID int64) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
rawAuthKeyID, _ := RawAuthKeyIDFrom(ctx)
|
||||
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
|
||||
r.announceSessionOnline(ctx, userID)
|
||||
return
|
||||
}
|
||||
r.deps.Sessions.BindUser(sessionID, userID)
|
||||
r.deps.Sessions.BindUserForAuthKey(rawAuthKeyIDForOrigin(ctx), sessionID, userID)
|
||||
r.announceSessionOnline(ctx, userID)
|
||||
}
|
||||
|
||||
|
|
@ -776,13 +753,7 @@ func (r *Router) pushSignInServiceNotificationToOthers(ctx context.Context, u do
|
|||
go func() {
|
||||
pushCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
if sent, err := scoped.PushToUserExceptAuthKeySession(pushCtx, u.ID, rawAuthKeyID, sessionID, proto.MessageFromServer, notification); err != nil {
|
||||
r.log.Debug("push sign-in service notification", zap.Int64("user_id", u.ID), zap.Int("sent", sent), zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
if sent, err := r.deps.Sessions.PushToUserExceptSession(pushCtx, u.ID, sessionID, proto.MessageFromServer, notification); err != nil {
|
||||
if sent, err := r.deps.Sessions.PushToUserExceptAuthKeySession(pushCtx, u.ID, rawAuthKeyID, sessionID, proto.MessageFromServer, notification); err != nil {
|
||||
r.log.Debug("push sign-in service notification", zap.Int64("user_id", u.ID), zap.Int("sent", sent), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func newRecoveryFanoutSessions(onlineChannels []int64, release <-chan struct{})
|
|||
}
|
||||
}
|
||||
|
||||
func (s *recoveryFanoutSessions) PushToUserExceptSession(ctx context.Context, _ int64, _ int64, _ proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *recoveryFanoutSessions) PushToUserExceptAuthKeySession(ctx context.Context, _ int64, _ [8]byte, _ int64, _ proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
s.startOnce.Do(func() { close(s.pushStarted) })
|
||||
if s.pushRelease != nil {
|
||||
select {
|
||||
|
|
@ -397,7 +397,7 @@ func newOverflowNudgeSessions(onlineByChannel map[int64][]int64) *overflowNudgeS
|
|||
}
|
||||
}
|
||||
|
||||
func (s *overflowNudgeSessions) PushToUserExceptSession(ctx context.Context, userID, excludeSessionID int64, typ proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *overflowNudgeSessions) PushToUserExceptAuthKeySession(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, typ proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
if updates, ok := msg.(*tg.Updates); ok && len(updates.Updates) == 1 {
|
||||
if nudge, ok := updates.Updates[0].(*tg.UpdateChannelTooLong); ok {
|
||||
pts, _ := nudge.GetPts()
|
||||
|
|
@ -407,7 +407,7 @@ func (s *overflowNudgeSessions) PushToUserExceptSession(ctx context.Context, use
|
|||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
return s.captureSessions.PushToUserExceptSession(ctx, userID, excludeSessionID, typ, msg)
|
||||
return s.captureSessions.PushToUserExceptAuthKeySession(ctx, userID, excludeAuthKeyID, excludeSessionID, typ, msg)
|
||||
}
|
||||
|
||||
func (s *overflowNudgeSessions) OnlineChannelMemberUserIDsExcluding(channelID int64, exclude map[int64]struct{}, limit int) []int64 {
|
||||
|
|
@ -445,11 +445,11 @@ func newNudgeSessions(online []int64) *nudgeSessions {
|
|||
return &nudgeSessions{captureSessions: &captureSessions{}, online: online, byUser: map[int64]bin.Encoder{}}
|
||||
}
|
||||
|
||||
func (s *nudgeSessions) PushToUserExceptSession(ctx context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *nudgeSessions) PushToUserExceptAuthKeySession(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
s.mu.Lock()
|
||||
s.byUser[userID] = msg
|
||||
s.mu.Unlock()
|
||||
return s.captureSessions.PushToUserExceptSession(ctx, userID, excludeSessionID, t, msg)
|
||||
return s.captureSessions.PushToUserExceptAuthKeySession(ctx, userID, excludeAuthKeyID, excludeSessionID, t, msg)
|
||||
}
|
||||
|
||||
func (s *nudgeSessions) OnlineChannelMemberUserIDsExcluding(_ int64, exclude map[int64]struct{}, limit int) []int64 {
|
||||
|
|
|
|||
|
|
@ -29,26 +29,12 @@ func (r *Router) currentUserID(ctx context.Context) (int64, bool, error) {
|
|||
}
|
||||
if r.deps.Sessions != nil {
|
||||
if sessionID, ok := SessionIDFrom(ctx); ok {
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
if rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx); ok {
|
||||
if userID, resolved := scoped.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
|
||||
if userID == 0 {
|
||||
if authKeyID, ok := AuthKeyIDFrom(ctx); ok {
|
||||
if cachedUserID, ok := r.positiveCachedAuthUser(authKeyID); ok {
|
||||
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, cachedUserID)
|
||||
r.announceSessionOnline(ctx, cachedUserID)
|
||||
return cachedUserID, true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return userID, userID != 0, nil
|
||||
}
|
||||
}
|
||||
} else if userID, resolved := r.deps.Sessions.UserIDResolved(sessionID); resolved {
|
||||
rawAuthKeyID := rawAuthKeyIDForOrigin(ctx)
|
||||
if userID, resolved := r.deps.Sessions.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
|
||||
if userID == 0 {
|
||||
if authKeyID, ok := AuthKeyIDFrom(ctx); ok {
|
||||
if cachedUserID, ok := r.positiveCachedAuthUser(authKeyID); ok {
|
||||
r.deps.Sessions.BindUser(sessionID, cachedUserID)
|
||||
r.deps.Sessions.BindUserForAuthKey(rawAuthKeyID, sessionID, cachedUserID)
|
||||
r.announceSessionOnline(ctx, cachedUserID)
|
||||
return cachedUserID, true, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,36 +49,26 @@ type AuthService interface {
|
|||
}
|
||||
|
||||
// SessionBinder 抽象登录后 session 与 user 的在线绑定。
|
||||
//
|
||||
// MTProto session 的完整身份是 raw auth_key_id + session_id。所有定位单个 session
|
||||
// 的方法都必须携带这两个值;禁止退回只按 session_id 查询,否则不同 auth key 复用
|
||||
// 同一随机 session_id 时会产生跨账号绑定、排除或推送歧义。
|
||||
type SessionBinder interface {
|
||||
BindAuthKey(sessionID int64, authKeyID [8]byte)
|
||||
AuthKeyID(sessionID int64) ([8]byte, bool)
|
||||
BindUser(sessionID, userID int64)
|
||||
UserID(sessionID int64) (int64, bool)
|
||||
UserIDResolved(sessionID int64) (userID int64, resolved bool)
|
||||
UnbindAuthKey(authKeyID [8]byte) int
|
||||
SetReceivesUpdates(sessionID int64, receives bool)
|
||||
PushToSession(ctx context.Context, sessionID int64, t proto.MessageType, msg bin.Encoder) error
|
||||
PushToUserExceptSession(ctx context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error)
|
||||
}
|
||||
|
||||
// ScopedSessionBinder 是 SessionBinder 的精确版本:所有定位都带 raw auth_key_id + session_id。
|
||||
// 生产 mtprotoedge.SessionManager 实现它;测试替身和旧实现可以只实现 SessionBinder。
|
||||
type ScopedSessionBinder interface {
|
||||
BindAuthKeyForSession(rawAuthKeyID [8]byte, sessionID int64, authKeyID [8]byte)
|
||||
AuthKeyIDForSession(rawAuthKeyID [8]byte, sessionID int64) ([8]byte, bool)
|
||||
BindUserForAuthKey(rawAuthKeyID [8]byte, sessionID, userID int64)
|
||||
UserIDForAuthKey(rawAuthKeyID [8]byte, sessionID int64) (int64, bool)
|
||||
UserIDResolvedForAuthKey(rawAuthKeyID [8]byte, sessionID int64) (userID int64, resolved bool)
|
||||
UnbindAuthKey(authKeyID [8]byte) int
|
||||
SetReceivesUpdatesForAuthKey(rawAuthKeyID [8]byte, sessionID int64, receives bool)
|
||||
PushToSessionForAuthKey(ctx context.Context, rawAuthKeyID [8]byte, sessionID int64, t proto.MessageType, msg bin.Encoder) error
|
||||
// excludeAuthKeyID is the physical/raw auth key, paired with session_id.
|
||||
// excludeAuthKeyID/excludeSessionID 必须同时为零(不排除)或同时非零(精确排除)。
|
||||
PushToUserExceptAuthKeySession(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error)
|
||||
}
|
||||
|
||||
// ScopedImmediateSessionPusher 是可选的登录前信号直推能力。
|
||||
// ImmediateSessionPusher 是可选的登录前信号直推能力。
|
||||
// 它绕过登录后 updates-ready 队列,只能用于会解锁登录流程本身的握手消息,
|
||||
// 例如 updateLoginToken。
|
||||
type ScopedImmediateSessionPusher interface {
|
||||
type ImmediateSessionPusher interface {
|
||||
PushToSessionForAuthKeyImmediate(ctx context.Context, rawAuthKeyID [8]byte, sessionID int64, t proto.MessageType, msg bin.Encoder) error
|
||||
}
|
||||
|
||||
|
|
@ -110,13 +100,9 @@ type RawSessionTerminator interface {
|
|||
CloseSessionsForRawAuthKeyExcept(authKeyID [8]byte, exceptSessionID int64) int
|
||||
}
|
||||
|
||||
// BestEffortSessionBinder 是 updates fanout 的短超时推送接口;不用于 RPC result/ack。
|
||||
// BestEffortSessionBinder 是带 raw auth_key_id 精确排除当前设备的短超时推送接口;
|
||||
// 不用于 RPC result/ack。
|
||||
type BestEffortSessionBinder interface {
|
||||
PushToUserExceptSessionBestEffort(ctx context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder, timeout time.Duration) (int, error)
|
||||
}
|
||||
|
||||
// ScopedBestEffortSessionBinder 是带 raw auth_key_id 精确排除当前设备的 best-effort 版本。
|
||||
type ScopedBestEffortSessionBinder interface {
|
||||
PushToUserExceptAuthKeySessionBestEffort(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder, timeout time.Duration) (int, error)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ const (
|
|||
defaultOutboxMaxIdleInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
var errMissingOutboxEvent = errors.New("missing outbox update event")
|
||||
var (
|
||||
errMissingOutboxEvent = errors.New("missing outbox update event")
|
||||
errInvalidOutboxExclusionPair = errors.New("outbox exclusion requires both raw auth key and session id")
|
||||
)
|
||||
|
||||
// OutboxDispatcher 把 PG transactional outbox 中的 update 批量推给在线 session。
|
||||
// 多 worker 并发 claim:ClaimPending 用 FOR UPDATE SKIP LOCKED,worker 间认领不重叠。
|
||||
|
|
@ -273,6 +276,23 @@ type outboxEventKey struct {
|
|||
|
||||
// dispatchBatch 批量加载已 claim 事件、逐条 push、批量标记 delivered;失败项单独退避重试。
|
||||
func (d *OutboxDispatcher) dispatchBatch(ctx context.Context, items []store.DispatchOutboxItem, loader batchEventLoader, marker batchOutboxMarker) {
|
||||
valid := make([]store.DispatchOutboxItem, 0, len(items))
|
||||
blockedUsers := make(map[int64]struct{})
|
||||
for _, item := range items {
|
||||
if _, blocked := blockedUsers[item.TargetUserID]; blocked {
|
||||
continue
|
||||
}
|
||||
if err := validateOutboxExclusionPair(item); err != nil {
|
||||
d.markDispatchFailed(ctx, item, err)
|
||||
blockedUsers[item.TargetUserID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
valid = append(valid, item)
|
||||
}
|
||||
if len(valid) == 0 {
|
||||
return
|
||||
}
|
||||
items = valid
|
||||
cursors := make([]store.EventCursor, len(items))
|
||||
for i, item := range items {
|
||||
cursors[i] = store.EventCursor{UserID: item.TargetUserID, Pts: item.Pts}
|
||||
|
|
@ -299,7 +319,7 @@ func (d *OutboxDispatcher) dispatchBatch(ctx context.Context, items []store.Disp
|
|||
start := time.Now()
|
||||
ready := make([]outboxDispatchReady, 0, len(items))
|
||||
requests := make([]OutboxUpdateRequest, 0, len(items))
|
||||
blockedUsers := make(map[int64]struct{})
|
||||
clear(blockedUsers)
|
||||
for _, item := range items {
|
||||
if _, blocked := blockedUsers[item.TargetUserID]; blocked {
|
||||
continue
|
||||
|
|
@ -362,6 +382,10 @@ type outboxDispatchReady struct {
|
|||
|
||||
func (d *OutboxDispatcher) dispatchItem(ctx context.Context, item store.DispatchOutboxItem) bool {
|
||||
start := time.Now()
|
||||
if err := validateOutboxExclusionPair(item); err != nil {
|
||||
d.markDispatchFailed(ctx, item, err)
|
||||
return false
|
||||
}
|
||||
events, err := d.events.ListAfter(ctx, item.TargetUserID, item.Pts-1, 1)
|
||||
if err != nil {
|
||||
d.markDispatchFailed(ctx, item, err)
|
||||
|
|
@ -443,23 +467,29 @@ func (d *OutboxDispatcher) buildOutboxUpdates(ctx context.Context, requests []Ou
|
|||
// 接口剩余的非 context 错误通常是确定性的编码/构造错误,必须进入 failed,不能永久占着
|
||||
// dispatching head 靠租约空转。只有 dispatcher shutdown/deadline 属于可重试中断。
|
||||
func (d *OutboxDispatcher) pushOutboxUpdate(ctx context.Context, item store.DispatchOutboxItem, update *tg.Updates) (sent int, retriable bool, err error) {
|
||||
var zeroAuthKeyID [8]byte
|
||||
if err := validateOutboxExclusionPair(item); err != nil {
|
||||
return 0, false, errInvalidOutboxExclusionPair
|
||||
}
|
||||
|
||||
if d.pushTimeout > 0 {
|
||||
if scoped, ok := d.sessions.(ScopedBestEffortSessionBinder); ok && item.ExcludeAuthKeyID != zeroAuthKeyID {
|
||||
sent, err = scoped.PushToUserExceptAuthKeySessionBestEffort(ctx, item.TargetUserID, item.ExcludeAuthKeyID, item.ExcludeSessionID, proto.MessageFromServer, update, d.pushTimeout)
|
||||
return sent, outboxPushInterrupted(err), err
|
||||
}
|
||||
if bestEffort, ok := d.sessions.(BestEffortSessionBinder); ok {
|
||||
sent, err = bestEffort.PushToUserExceptSessionBestEffort(ctx, item.TargetUserID, item.ExcludeSessionID, proto.MessageFromServer, update, d.pushTimeout)
|
||||
sent, err = bestEffort.PushToUserExceptAuthKeySessionBestEffort(ctx, item.TargetUserID, item.ExcludeAuthKeyID, item.ExcludeSessionID, proto.MessageFromServer, update, d.pushTimeout)
|
||||
return sent, outboxPushInterrupted(err), err
|
||||
}
|
||||
}
|
||||
if scoped, ok := d.sessions.(ScopedSessionBinder); ok && item.ExcludeAuthKeyID != zeroAuthKeyID {
|
||||
sent, err = scoped.PushToUserExceptAuthKeySession(ctx, item.TargetUserID, item.ExcludeAuthKeyID, item.ExcludeSessionID, proto.MessageFromServer, update)
|
||||
return sent, false, err
|
||||
sent, err = d.sessions.PushToUserExceptAuthKeySession(ctx, item.TargetUserID, item.ExcludeAuthKeyID, item.ExcludeSessionID, proto.MessageFromServer, update)
|
||||
if err != nil {
|
||||
return sent, outboxPushInterrupted(err), err
|
||||
}
|
||||
sent, err = d.sessions.PushToUserExceptSession(ctx, item.TargetUserID, item.ExcludeSessionID, proto.MessageFromServer, update)
|
||||
return sent, false, err
|
||||
return sent, false, nil
|
||||
}
|
||||
|
||||
func validateOutboxExclusionPair(item store.DispatchOutboxItem) error {
|
||||
var zeroAuthKeyID [8]byte
|
||||
if (item.ExcludeAuthKeyID != zeroAuthKeyID) != (item.ExcludeSessionID != 0) {
|
||||
return errInvalidOutboxExclusionPair
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func outboxPushInterrupted(err error) bool {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ func TestOutboxDispatcherPushesNewMessageAndMarksDelivered(t *testing.T) {
|
|||
TargetUserID: msg.OwnerUserID,
|
||||
Pts: msg.Pts,
|
||||
EventType: domain.UpdateEventNewMessage,
|
||||
ExcludeAuthKeyID: [8]byte{1},
|
||||
ExcludeSessionID: 99,
|
||||
}}}
|
||||
events := &captureUpdateEventStore{events: []domain.UpdateEvent{{
|
||||
|
|
@ -103,6 +104,78 @@ func TestOutboxDispatcherUsesScopedAuthKeyExclusion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOutboxDispatcherRejectsPartialSessionExclusion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
authKeyID [8]byte
|
||||
sessionID int64
|
||||
}{
|
||||
{name: "auth key only", authKeyID: [8]byte{1}},
|
||||
{name: "session only", sessionID: 99},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
const userID = int64(1000000002)
|
||||
outbox := &captureDispatchOutbox{items: []store.DispatchOutboxItem{{
|
||||
ID: 58,
|
||||
TargetUserID: userID,
|
||||
Pts: 10,
|
||||
EventType: domain.UpdateEventPeerSettings,
|
||||
ExcludeAuthKeyID: tt.authKeyID,
|
||||
ExcludeSessionID: tt.sessionID,
|
||||
}}}
|
||||
// No event exists: exclusion shape must win before event loading so a bad
|
||||
// durable row is not mislabeled as merely missing its payload.
|
||||
events := &captureUpdateEventStore{}
|
||||
sessions := &captureSessions{}
|
||||
metrics := &captureOutboxMetrics{}
|
||||
dispatcher := NewOutboxDispatcher(events, outbox, sessions, zaptest.NewLogger(t), WithOutboxMetrics(metrics))
|
||||
dispatcher.DispatchOnce(context.Background())
|
||||
|
||||
if !outbox.failed || outbox.delivered {
|
||||
t.Fatalf("outbox failed=%v delivered=%v, want failed without delivery", outbox.failed, outbox.delivered)
|
||||
}
|
||||
if outbox.failedError != errInvalidOutboxExclusionPair.Error() {
|
||||
t.Fatalf("failed error = %q, want %q", outbox.failedError, errInvalidOutboxExclusionPair)
|
||||
}
|
||||
if sessions.message != nil {
|
||||
t.Fatalf("invalid exclusion unexpectedly pushed %T", sessions.message)
|
||||
}
|
||||
if metrics.failed != 1 || metrics.delivered != 0 {
|
||||
t.Fatalf("metrics failed=%d delivered=%d, want 1/0", metrics.failed, metrics.delivered)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutboxDispatcherBatchRejectsPartialExclusionBeforeNoop(t *testing.T) {
|
||||
const userID = int64(1000000002)
|
||||
events := &batchEventStore{captureUpdateEventStore: &captureUpdateEventStore{events: []domain.UpdateEvent{{
|
||||
UserID: userID,
|
||||
Type: domain.UpdateEventNoop,
|
||||
Pts: 10,
|
||||
}}}}
|
||||
outbox := &batchDispatchOutbox{captureDispatchOutbox: &captureDispatchOutbox{items: []store.DispatchOutboxItem{{
|
||||
ID: 59,
|
||||
TargetUserID: userID,
|
||||
Pts: 10,
|
||||
EventType: domain.UpdateEventNoop,
|
||||
ExcludeAuthKeyID: [8]byte{1},
|
||||
}}}}
|
||||
dispatcher := NewOutboxDispatcher(events, outbox, &captureSessions{}, zaptest.NewLogger(t))
|
||||
dispatcher.DispatchOnce(context.Background())
|
||||
|
||||
if !outbox.failed || outbox.delivered || len(outbox.deliveredBatch) != 0 {
|
||||
t.Fatalf("batch invalid pair failed=%v delivered=%v batch=%v", outbox.failed, outbox.delivered, outbox.deliveredBatch)
|
||||
}
|
||||
if outbox.failedError != errInvalidOutboxExclusionPair.Error() {
|
||||
t.Fatalf("failed error = %q, want %q", outbox.failedError, errInvalidOutboxExclusionPair)
|
||||
}
|
||||
if len(events.batchCursors) != 0 {
|
||||
t.Fatalf("invalid pair reached batch event loader: %+v", events.batchCursors)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOutboxDispatcherBatchPath 覆盖生产批量路径:store 同时具备 BatchByCursor + MarkDeliveredBatch
|
||||
// 时,DispatchOnce 一次批量取事件、推送、再批量标记 delivered,而非逐条。
|
||||
func TestOutboxDispatcherBatchPath(t *testing.T) {
|
||||
|
|
@ -129,6 +202,7 @@ func TestOutboxDispatcherBatchPath(t *testing.T) {
|
|||
TargetUserID: msg.OwnerUserID,
|
||||
Pts: msg.Pts,
|
||||
EventType: domain.UpdateEventNewMessage,
|
||||
ExcludeAuthKeyID: [8]byte{1},
|
||||
ExcludeSessionID: 99,
|
||||
}}}}
|
||||
sessions := &captureSessions{}
|
||||
|
|
@ -661,22 +735,34 @@ func TestOutboxDispatcherUsesBestEffortPush(t *testing.T) {
|
|||
Message: msg,
|
||||
Users: []domain.User{{ID: msg.From.ID, FirstName: "Sender"}},
|
||||
}}}
|
||||
outbox := &captureDispatchOutbox{items: []store.DispatchOutboxItem{{
|
||||
ID: 55,
|
||||
TargetUserID: msg.OwnerUserID,
|
||||
Pts: msg.Pts,
|
||||
EventType: domain.UpdateEventNewMessage,
|
||||
ExcludeSessionID: 99,
|
||||
}}}
|
||||
sessions := &captureBestEffortSessions{captureSessions: &captureSessions{}}
|
||||
dispatcher := NewOutboxDispatcher(events, outbox, sessions, zaptest.NewLogger(t), WithOutboxPushTimeout(50*time.Millisecond))
|
||||
dispatcher.DispatchOnce(context.Background())
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
authKeyID [8]byte
|
||||
sessionID int64
|
||||
}{
|
||||
{name: "exclude origin", authKeyID: [8]byte{1}, sessionID: 99},
|
||||
{name: "exclude none"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
outbox := &captureDispatchOutbox{items: []store.DispatchOutboxItem{{
|
||||
ID: 55,
|
||||
TargetUserID: msg.OwnerUserID,
|
||||
Pts: msg.Pts,
|
||||
EventType: domain.UpdateEventNewMessage,
|
||||
ExcludeAuthKeyID: tt.authKeyID,
|
||||
ExcludeSessionID: tt.sessionID,
|
||||
}}}
|
||||
sessions := &captureBestEffortSessions{captureSessions: &captureSessions{}}
|
||||
dispatcher := NewOutboxDispatcher(events, outbox, sessions, zaptest.NewLogger(t), WithOutboxPushTimeout(50*time.Millisecond))
|
||||
dispatcher.DispatchOnce(context.Background())
|
||||
|
||||
if !sessions.bestEffort || sessions.timeout != 50*time.Millisecond {
|
||||
t.Fatalf("best-effort push = %v timeout %v, want true/50ms", sessions.bestEffort, sessions.timeout)
|
||||
}
|
||||
if !outbox.delivered || outbox.failed {
|
||||
t.Fatalf("outbox delivered=%v failed=%v, want delivered after accepted best-effort push", outbox.delivered, outbox.failed)
|
||||
if !sessions.bestEffort || sessions.timeout != 50*time.Millisecond {
|
||||
t.Fatalf("best-effort push = %v timeout %v, want true/50ms", sessions.bestEffort, sessions.timeout)
|
||||
}
|
||||
if !outbox.delivered || outbox.failed {
|
||||
t.Fatalf("outbox delivered=%v failed=%v, want delivered after accepted best-effort push", outbox.delivered, outbox.failed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -686,10 +772,10 @@ type captureBestEffortSessions struct {
|
|||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (s *captureBestEffortSessions) PushToUserExceptSessionBestEffort(ctx context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder, timeout time.Duration) (int, error) {
|
||||
func (s *captureBestEffortSessions) PushToUserExceptAuthKeySessionBestEffort(ctx context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder, timeout time.Duration) (int, error) {
|
||||
s.bestEffort = true
|
||||
s.timeout = timeout
|
||||
return s.PushToUserExceptSession(ctx, userID, excludeSessionID, t, msg)
|
||||
return s.PushToUserExceptAuthKeySession(ctx, userID, excludeAuthKeyID, excludeSessionID, t, msg)
|
||||
}
|
||||
|
||||
type orderedOutboxCaptureSessions struct {
|
||||
|
|
@ -709,7 +795,7 @@ type selectiveFailOutboxSessions struct {
|
|||
attempts []outboxPushAttempt
|
||||
}
|
||||
|
||||
func (s *selectiveFailOutboxSessions) PushToUserExceptSession(_ context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *selectiveFailOutboxSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
pts := 0
|
||||
if updates, ok := msg.(*tg.Updates); ok {
|
||||
pts = firstOutboxUpdatePts(updates)
|
||||
|
|
@ -718,18 +804,18 @@ func (s *selectiveFailOutboxSessions) PushToUserExceptSession(_ context.Context,
|
|||
if userID == s.failUserID && pts == s.failPts {
|
||||
return 0, errors.New("injected outbox push failure")
|
||||
}
|
||||
return s.captureSessions.PushToUserExceptSession(context.Background(), userID, excludeSessionID, t, msg)
|
||||
return s.captureSessions.PushToUserExceptAuthKeySession(context.Background(), userID, excludeAuthKeyID, excludeSessionID, t, msg)
|
||||
}
|
||||
|
||||
func (s *selectiveFailOutboxSessions) pushAttempts() []outboxPushAttempt {
|
||||
return append([]outboxPushAttempt(nil), s.attempts...)
|
||||
}
|
||||
|
||||
func (s *orderedOutboxCaptureSessions) PushToUserExceptSession(_ context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *orderedOutboxCaptureSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
if updates, ok := msg.(*tg.Updates); ok {
|
||||
s.pushed = append(s.pushed, firstOutboxUpdatePts(updates))
|
||||
}
|
||||
return s.captureSessions.PushToUserExceptSession(context.Background(), userID, excludeSessionID, t, msg)
|
||||
return s.captureSessions.PushToUserExceptAuthKeySession(context.Background(), userID, excludeAuthKeyID, excludeSessionID, t, msg)
|
||||
}
|
||||
|
||||
func (s *orderedOutboxCaptureSessions) pushedPts() []int {
|
||||
|
|
@ -916,32 +1002,30 @@ func (s *captureScopedSessions) immediatePushSnapshot() (proto.MessageType, bin.
|
|||
}
|
||||
|
||||
func (s *captureScopedSessions) BindAuthKeyForSession(rawAuthKeyID [8]byte, sessionID int64, authKeyID [8]byte) {
|
||||
s.BindAuthKey(sessionID, authKeyID)
|
||||
s.captureSessions.BindAuthKeyForSession(rawAuthKeyID, sessionID, authKeyID)
|
||||
s.setScopedAuthKeyID(rawAuthKeyID)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) AuthKeyIDForSession([8]byte, int64) ([8]byte, bool) {
|
||||
return s.AuthKeyID(0)
|
||||
func (s *captureScopedSessions) AuthKeyIDForSession(rawAuthKeyID [8]byte, sessionID int64) ([8]byte, bool) {
|
||||
return s.captureSessions.AuthKeyIDForSession(rawAuthKeyID, sessionID)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) BindUserForAuthKey(rawAuthKeyID [8]byte, sessionID, userID int64) {
|
||||
s.BindUser(sessionID, userID)
|
||||
s.captureSessions.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
|
||||
s.setScopedAuthKeyID(rawAuthKeyID)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) UserIDForAuthKey([8]byte, int64) (int64, bool) {
|
||||
return s.UserID(0)
|
||||
func (s *captureScopedSessions) UserIDResolvedForAuthKey(rawAuthKeyID [8]byte, sessionID int64) (int64, bool) {
|
||||
return s.captureSessions.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) UserIDResolvedForAuthKey([8]byte, int64) (int64, bool) {
|
||||
return s.UserIDResolved(0)
|
||||
func (s *captureScopedSessions) SetReceivesUpdatesForAuthKey(rawAuthKeyID [8]byte, sessionID int64, receives bool) {
|
||||
s.captureSessions.SetReceivesUpdatesForAuthKey(rawAuthKeyID, sessionID, receives)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) SetReceivesUpdatesForAuthKey([8]byte, int64, bool) {}
|
||||
|
||||
func (s *captureScopedSessions) PushToSessionForAuthKey(_ context.Context, rawAuthKeyID [8]byte, sessionID int64, t proto.MessageType, msg bin.Encoder) error {
|
||||
s.setScopedAuthKeyID(rawAuthKeyID)
|
||||
return s.PushToSession(context.Background(), sessionID, t, msg)
|
||||
return s.captureSessions.PushToSessionForAuthKey(context.Background(), rawAuthKeyID, sessionID, t, msg)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) PushToSessionForAuthKeyImmediate(_ context.Context, rawAuthKeyID [8]byte, sessionID int64, t proto.MessageType, msg bin.Encoder) error {
|
||||
|
|
@ -951,12 +1035,12 @@ func (s *captureScopedSessions) PushToSessionForAuthKeyImmediate(_ context.Conte
|
|||
s.immediateType = t
|
||||
s.immediateMsg = msg
|
||||
s.scopedMu.Unlock()
|
||||
return s.PushToSession(context.Background(), sessionID, t, msg)
|
||||
return s.captureSessions.PushToSessionForAuthKey(context.Background(), rawAuthKeyID, sessionID, t, msg)
|
||||
}
|
||||
|
||||
func (s *captureScopedSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
s.setScopedAuthKeyID(excludeAuthKeyID)
|
||||
return s.PushToUserExceptSession(context.Background(), userID, excludeSessionID, t, msg)
|
||||
return s.captureSessions.PushToUserExceptAuthKeySession(context.Background(), userID, excludeAuthKeyID, excludeSessionID, t, msg)
|
||||
}
|
||||
|
||||
func (s *captureDispatchOutbox) ClaimPending(context.Context, int) ([]store.DispatchOutboxItem, error) {
|
||||
|
|
@ -1035,7 +1119,7 @@ type interruptedBestEffortSessions struct {
|
|||
attempts int
|
||||
}
|
||||
|
||||
func (s *interruptedBestEffortSessions) PushToUserExceptSessionBestEffort(_ context.Context, _ int64, _ int64, _ proto.MessageType, _ bin.Encoder, _ time.Duration) (int, error) {
|
||||
func (s *interruptedBestEffortSessions) PushToUserExceptAuthKeySessionBestEffort(_ context.Context, _ int64, _ [8]byte, _ int64, _ proto.MessageType, _ bin.Encoder, _ time.Duration) (int, error) {
|
||||
s.attempts++
|
||||
return 0, context.DeadlineExceeded
|
||||
}
|
||||
|
|
@ -1065,6 +1149,7 @@ func TestOutboxDispatcherDefersOnPushInterruption(t *testing.T) {
|
|||
TargetUserID: msg.OwnerUserID,
|
||||
Pts: msg.Pts,
|
||||
EventType: domain.UpdateEventNewMessage,
|
||||
ExcludeAuthKeyID: [8]byte{1},
|
||||
ExcludeSessionID: 99,
|
||||
}}}
|
||||
sessions := &interruptedBestEffortSessions{captureSessions: &captureSessions{}}
|
||||
|
|
|
|||
|
|
@ -63,11 +63,9 @@ func (r *Router) pushPhoneSignalingData(ctx context.Context, targetUserID int64,
|
|||
Date: int(r.clock.Now().Unix()),
|
||||
Seq: 0,
|
||||
}
|
||||
if !device.Zero() {
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
if err := scoped.PushToSessionForAuthKey(ctx, device.RawAuthKeyID, device.SessionID, proto.MessageFromServer, upd); err == nil {
|
||||
return
|
||||
}
|
||||
if !device.Zero() && r.deps.Sessions != nil {
|
||||
if err := r.deps.Sessions.PushToSessionForAuthKey(ctx, device.RawAuthKeyID, device.SessionID, proto.MessageFromServer, upd); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
r.pushUserMessage(ctx, targetUserID, "phone call signaling", upd)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
// phonePushRecord 记录一次定向推送(目标用户、被排除的 session、载荷)。
|
||||
type phonePushRecord struct {
|
||||
userID int64
|
||||
targetSession int64
|
||||
excludeSession int64
|
||||
msg bin.Encoder
|
||||
}
|
||||
|
|
@ -35,18 +36,25 @@ type phoneCaptureSessions struct {
|
|||
log []phonePushRecord
|
||||
}
|
||||
|
||||
func (s *phoneCaptureSessions) BindAuthKey(int64, [8]byte) {}
|
||||
func (s *phoneCaptureSessions) AuthKeyID(int64) ([8]byte, bool) { return [8]byte{}, false }
|
||||
func (s *phoneCaptureSessions) BindUser(int64, int64) {}
|
||||
func (s *phoneCaptureSessions) UserID(int64) (int64, bool) { return 0, false }
|
||||
func (s *phoneCaptureSessions) UserIDResolved(int64) (int64, bool) { return 0, false }
|
||||
func (s *phoneCaptureSessions) UnbindAuthKey([8]byte) int { return 0 }
|
||||
func (s *phoneCaptureSessions) SetReceivesUpdates(int64, bool) {}
|
||||
func (s *phoneCaptureSessions) PushToSession(context.Context, int64, proto.MessageType, bin.Encoder) error {
|
||||
func (s *phoneCaptureSessions) BindAuthKeyForSession([8]byte, int64, [8]byte) {}
|
||||
func (s *phoneCaptureSessions) AuthKeyIDForSession([8]byte, int64) ([8]byte, bool) {
|
||||
return [8]byte{}, false
|
||||
}
|
||||
func (s *phoneCaptureSessions) BindUserForAuthKey([8]byte, int64, int64) {}
|
||||
func (s *phoneCaptureSessions) UserIDResolvedForAuthKey([8]byte, int64) (int64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
func (s *phoneCaptureSessions) UnbindAuthKey([8]byte) int { return 0 }
|
||||
func (s *phoneCaptureSessions) SetReceivesUpdatesForAuthKey([8]byte, int64, bool) {}
|
||||
|
||||
func (s *phoneCaptureSessions) PushToSessionForAuthKey(_ context.Context, _ [8]byte, sessionID int64, _ proto.MessageType, msg bin.Encoder) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.log = append(s.log, phonePushRecord{targetSession: sessionID, msg: msg})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *phoneCaptureSessions) PushToUserExceptSession(_ context.Context, userID, excludeSessionID int64, _ proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *phoneCaptureSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, _ [8]byte, excludeSessionID int64, _ proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.log = append(s.log, phonePushRecord{userID: userID, excludeSession: excludeSessionID, msg: msg})
|
||||
|
|
@ -308,8 +316,8 @@ func TestPhoneCallRPCHappyPath(t *testing.T) {
|
|||
t.Fatalf("sendSignalingData = %v err=%v", okSig, err)
|
||||
}
|
||||
pushes = f.sessions.records()
|
||||
if len(pushes) != 1 || pushes[0].userID != f.callee.ID {
|
||||
t.Fatalf("signaling pushes = %+v, want one to callee", pushes)
|
||||
if len(pushes) != 1 || pushes[0].targetSession != phoneCalleeSession {
|
||||
t.Fatalf("signaling pushes = %+v, want one to callee session", pushes)
|
||||
}
|
||||
sigUpdates := pushes[0].msg.(*tg.Updates)
|
||||
sig, ok := sigUpdates.Updates[0].(*tg.UpdatePhoneCallSignalingData)
|
||||
|
|
|
|||
|
|
@ -567,20 +567,11 @@ func (r *Router) pushSelfPhotoUpdateToCurrentSession(ctx context.Context, update
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
rawAuthKeyID, hasRawAuthKeyID := RawAuthKeyIDFrom(ctx)
|
||||
rawAuthKeyID := rawAuthKeyIDForOrigin(ctx)
|
||||
push := func() {
|
||||
pushCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
if !hasRawAuthKeyID {
|
||||
return
|
||||
}
|
||||
if err := scoped.PushToSessionForAuthKey(pushCtx, rawAuthKeyID, sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
r.log.Debug("push self photo update to current session", zap.Int64("session_id", sessionID), zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := r.deps.Sessions.PushToSession(pushCtx, sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
if err := r.deps.Sessions.PushToSessionForAuthKey(pushCtx, rawAuthKeyID, sessionID, proto.MessageFromServer, updates); err != nil {
|
||||
r.log.Debug("push self photo update to current session", zap.Int64("session_id", sessionID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,16 +15,8 @@ func (r *Router) pushUserMessage(ctx context.Context, userID int64, logMessage s
|
|||
sessionID, _ := SessionIDFrom(ctx)
|
||||
if timeout := r.cfg.OutboundPushTimeout; timeout > 0 {
|
||||
authKeyID := rawAuthKeyIDForOrigin(ctx)
|
||||
if scoped, ok := r.deps.Sessions.(ScopedBestEffortSessionBinder); ok {
|
||||
if sent, err := scoped.PushToUserExceptAuthKeySessionBestEffort(ctx, userID, authKeyID, sessionID, proto.MessageFromServer, msg, timeout); err != nil {
|
||||
r.log.Debug(logMessage, zap.Int64("user_id", userID), zap.Int("sent", sent), zap.Duration("timeout", timeout), zap.Error(err))
|
||||
return sent
|
||||
} else {
|
||||
return sent
|
||||
}
|
||||
}
|
||||
if bestEffort, ok := r.deps.Sessions.(BestEffortSessionBinder); ok {
|
||||
if sent, err := bestEffort.PushToUserExceptSessionBestEffort(ctx, userID, sessionID, proto.MessageFromServer, msg, timeout); err != nil {
|
||||
if sent, err := bestEffort.PushToUserExceptAuthKeySessionBestEffort(ctx, userID, authKeyID, sessionID, proto.MessageFromServer, msg, timeout); err != nil {
|
||||
r.log.Debug(logMessage, zap.Int64("user_id", userID), zap.Int("sent", sent), zap.Duration("timeout", timeout), zap.Error(err))
|
||||
return sent
|
||||
} else {
|
||||
|
|
@ -32,16 +24,8 @@ func (r *Router) pushUserMessage(ctx context.Context, userID int64, logMessage s
|
|||
}
|
||||
}
|
||||
}
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
authKeyID := rawAuthKeyIDForOrigin(ctx)
|
||||
if sent, err := scoped.PushToUserExceptAuthKeySession(ctx, userID, authKeyID, sessionID, proto.MessageFromServer, msg); err != nil {
|
||||
r.log.Debug(logMessage, zap.Int64("user_id", userID), zap.Int("sent", sent), zap.Error(err))
|
||||
return sent
|
||||
} else {
|
||||
return sent
|
||||
}
|
||||
}
|
||||
if sent, err := r.deps.Sessions.PushToUserExceptSession(ctx, userID, sessionID, proto.MessageFromServer, msg); err != nil {
|
||||
authKeyID := rawAuthKeyIDForOrigin(ctx)
|
||||
if sent, err := r.deps.Sessions.PushToUserExceptAuthKeySession(ctx, userID, authKeyID, sessionID, proto.MessageFromServer, msg); err != nil {
|
||||
r.log.Debug(logMessage, zap.Int64("user_id", userID), zap.Int("sent", sent), zap.Error(err))
|
||||
return sent
|
||||
} else {
|
||||
|
|
@ -76,17 +60,7 @@ func (r *Router) pushCurrentSessionMessage(ctx context.Context, logMessage strin
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := scoped.PushToSessionForAuthKey(ctx, rawAuthKeyID, sessionID, proto.MessageFromServer, msg); err != nil {
|
||||
r.log.Debug(logMessage, zap.Int64("session_id", sessionID), zap.Error(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := r.deps.Sessions.PushToSession(ctx, sessionID, proto.MessageFromServer, msg); err != nil {
|
||||
if err := r.deps.Sessions.PushToSessionForAuthKey(ctx, rawAuthKeyIDForOrigin(ctx), sessionID, proto.MessageFromServer, msg); err != nil {
|
||||
r.log.Debug(logMessage, zap.Int64("session_id", sessionID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,12 +308,7 @@ func (r *Router) effectiveAuthKeyID(ctx context.Context, rawAuthKeyID [8]byte, s
|
|||
hasCached bool
|
||||
)
|
||||
if r.deps.Sessions != nil {
|
||||
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
|
||||
if id, ok := scoped.AuthKeyIDForSession(rawAuthKeyID, sessionID); ok {
|
||||
cached = id
|
||||
hasCached = true
|
||||
}
|
||||
} else if id, ok := r.deps.Sessions.AuthKeyID(sessionID); ok {
|
||||
if id, ok := r.deps.Sessions.AuthKeyIDForSession(rawAuthKeyID, sessionID); ok {
|
||||
cached = id
|
||||
hasCached = true
|
||||
}
|
||||
|
|
@ -381,39 +376,22 @@ func (r *Router) effectiveAuthKeyID(ctx context.Context, rawAuthKeyID [8]byte, s
|
|||
|
||||
func (r *Router) bindEffectiveAuthKey(rawAuthKeyID [8]byte, sessionID int64, effective [8]byte) {
|
||||
if r.deps.Sessions != nil {
|
||||
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
|
||||
scoped.BindAuthKeyForSession(rawAuthKeyID, sessionID, effective)
|
||||
} else {
|
||||
r.deps.Sessions.BindAuthKey(sessionID, effective)
|
||||
}
|
||||
r.deps.Sessions.BindAuthKeyForSession(rawAuthKeyID, sessionID, effective)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) effectiveUserID(ctx context.Context, rawAuthKeyID, authKeyID [8]byte, sessionID int64) (int64, bool, error) {
|
||||
if userID, ok := UserIDFrom(ctx); ok {
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
|
||||
} else if r.deps.Sessions != nil {
|
||||
r.deps.Sessions.BindUser(sessionID, userID)
|
||||
if r.deps.Sessions != nil {
|
||||
r.deps.Sessions.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
|
||||
}
|
||||
return userID, true, nil
|
||||
}
|
||||
if r.deps.Sessions != nil {
|
||||
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
|
||||
if userID, resolved := scoped.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
|
||||
if userID == 0 {
|
||||
if cachedUserID, ok := r.positiveCachedAuthUser(authKeyID); ok {
|
||||
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, cachedUserID)
|
||||
r.announceSessionOnline(ctx, cachedUserID)
|
||||
return cachedUserID, true, nil
|
||||
}
|
||||
}
|
||||
return userID, userID != 0, nil
|
||||
}
|
||||
} else if userID, resolved := r.deps.Sessions.UserIDResolved(sessionID); resolved {
|
||||
if userID, resolved := r.deps.Sessions.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
|
||||
if userID == 0 {
|
||||
if cachedUserID, ok := r.positiveCachedAuthUser(authKeyID); ok {
|
||||
r.deps.Sessions.BindUser(sessionID, cachedUserID)
|
||||
r.deps.Sessions.BindUserForAuthKey(rawAuthKeyID, sessionID, cachedUserID)
|
||||
r.announceSessionOnline(ctx, cachedUserID)
|
||||
return cachedUserID, true, nil
|
||||
}
|
||||
|
|
@ -434,32 +412,16 @@ func (r *Router) effectiveUserID(ctx context.Context, rawAuthKeyID, authKeyID [8
|
|||
return 0, false, err
|
||||
}
|
||||
if r.deps.Sessions != nil {
|
||||
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
|
||||
if cachedUserID, resolved := scoped.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
|
||||
if cachedUserID != 0 || !found {
|
||||
return cachedUserID, cachedUserID != 0, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cachedUserID, resolved := r.deps.Sessions.UserIDResolved(sessionID); resolved {
|
||||
if cachedUserID != 0 || !found {
|
||||
return cachedUserID, cachedUserID != 0, nil
|
||||
}
|
||||
if cachedUserID, resolved := r.deps.Sessions.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
|
||||
if cachedUserID != 0 || !found {
|
||||
return cachedUserID, cachedUserID != 0, nil
|
||||
}
|
||||
}
|
||||
if found {
|
||||
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
|
||||
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
|
||||
} else {
|
||||
r.deps.Sessions.BindUser(sessionID, userID)
|
||||
}
|
||||
r.deps.Sessions.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
|
||||
r.announceSessionOnline(ctx, userID)
|
||||
} else {
|
||||
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
|
||||
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, 0)
|
||||
} else {
|
||||
r.deps.Sessions.BindUser(sessionID, 0)
|
||||
}
|
||||
r.deps.Sessions.BindUserForAuthKey(rawAuthKeyID, sessionID, 0)
|
||||
}
|
||||
}
|
||||
return userID, found, nil
|
||||
|
|
@ -532,14 +494,6 @@ func (r *Router) invalidateAuthUserCache(authKeyID [8]byte) {
|
|||
r.authUserSF.Forget(authKeyClientInfoSingleflightPrefix + key)
|
||||
}
|
||||
|
||||
func (r *Router) scopedSessions() (ScopedSessionBinder, bool) {
|
||||
if r.deps.Sessions == nil {
|
||||
return nil, false
|
||||
}
|
||||
scoped, ok := r.deps.Sessions.(ScopedSessionBinder)
|
||||
return scoped, ok
|
||||
}
|
||||
|
||||
func (r *Router) dispatch(ctx context.Context, b *bin.Buffer, depth int) (bin.Encoder, error) {
|
||||
if depth > maxWrapperDepth {
|
||||
return nil, wrapperTooDeepErr()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func newAuthBindingCaptureSessions() *authBindingCaptureSessions {
|
|||
return &authBindingCaptureSessions{captureSessions: &captureSessions{}}
|
||||
}
|
||||
|
||||
func (s *authBindingCaptureSessions) PushToUserExceptSession(_ context.Context, userID, _ int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *authBindingCaptureSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, _ [8]byte, _ int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.messageType = t
|
||||
|
|
@ -43,8 +43,8 @@ func TestDispatchPromotesNegativeSessionCacheFromPositiveAuthCache(t *testing.T)
|
|||
userID = int64(1000000001)
|
||||
)
|
||||
sessions := newAuthBindingCaptureSessions()
|
||||
sessions.BindAuthKey(sessionID, authKeyID)
|
||||
sessions.BindUser(sessionID, 0)
|
||||
sessions.BindAuthKeyForSession(authKeyID, sessionID, authKeyID)
|
||||
sessions.BindUserForAuthKey(authKeyID, sessionID, 0)
|
||||
auth := &captureAuthService{}
|
||||
r := New(Config{}, Deps{
|
||||
Auth: auth,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
type captureSessions struct {
|
||||
mu sync.Mutex
|
||||
rawAuthKeyID [8]byte
|
||||
sessionID int64
|
||||
userID int64
|
||||
userResolved bool
|
||||
|
|
@ -81,39 +82,35 @@ func (s *captureSessions) clearMessages() {
|
|||
s.pushUserIDs = nil
|
||||
}
|
||||
|
||||
func (s *captureSessions) BindAuthKey(sessionID int64, authKeyID [8]byte) {
|
||||
func (s *captureSessions) BindAuthKeyForSession(rawAuthKeyID [8]byte, sessionID int64, authKeyID [8]byte) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.authKeyResolved && s.authKeyID != authKeyID {
|
||||
s.userID = 0
|
||||
s.userResolved = false
|
||||
}
|
||||
s.rawAuthKeyID = rawAuthKeyID
|
||||
s.sessionID = sessionID
|
||||
s.authKeyID = authKeyID
|
||||
s.authKeyResolved = true
|
||||
}
|
||||
|
||||
func (s *captureSessions) AuthKeyID(int64) ([8]byte, bool) {
|
||||
func (s *captureSessions) AuthKeyIDForSession([8]byte, int64) ([8]byte, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.authKeyID, s.authKeyResolved
|
||||
}
|
||||
|
||||
func (s *captureSessions) BindUser(sessionID, userID int64) {
|
||||
func (s *captureSessions) BindUserForAuthKey(rawAuthKeyID [8]byte, sessionID, userID int64) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.rawAuthKeyID = rawAuthKeyID
|
||||
s.sessionID = sessionID
|
||||
s.userID = userID
|
||||
s.userResolved = true
|
||||
}
|
||||
|
||||
func (s *captureSessions) UserID(int64) (int64, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.userID, s.userID != 0
|
||||
}
|
||||
|
||||
func (s *captureSessions) UserIDResolved(int64) (int64, bool) {
|
||||
func (s *captureSessions) UserIDResolvedForAuthKey([8]byte, int64) (int64, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.userID, s.userResolved
|
||||
|
|
@ -130,26 +127,29 @@ func (s *captureSessions) UnbindAuthKey(authKeyID [8]byte) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (s *captureSessions) SetReceivesUpdates(sessionID int64, receives bool) {
|
||||
func (s *captureSessions) SetReceivesUpdatesForAuthKey(rawAuthKeyID [8]byte, sessionID int64, receives bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.rawAuthKeyID = rawAuthKeyID
|
||||
s.sessionID = sessionID
|
||||
s.receives = receives
|
||||
}
|
||||
|
||||
func (s *captureSessions) PushToSession(_ context.Context, sessionID int64, t proto.MessageType, msg bin.Encoder) error {
|
||||
func (s *captureSessions) PushToSessionForAuthKey(_ context.Context, rawAuthKeyID [8]byte, sessionID int64, t proto.MessageType, msg bin.Encoder) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.rawAuthKeyID = rawAuthKeyID
|
||||
s.sessionID = sessionID
|
||||
s.messageType = t
|
||||
s.message = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *captureSessions) PushToUserExceptSession(_ context.Context, userID, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
func (s *captureSessions) PushToUserExceptAuthKeySession(_ context.Context, userID int64, excludeAuthKeyID [8]byte, excludeSessionID int64, t proto.MessageType, msg bin.Encoder) (int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.userID = userID
|
||||
s.rawAuthKeyID = excludeAuthKeyID
|
||||
s.sessionID = excludeSessionID
|
||||
s.messageType = t
|
||||
s.message = msg
|
||||
|
|
|
|||
|
|
@ -192,13 +192,7 @@ func (r *Router) markSessionReceivesUpdates(ctx context.Context, userID int64) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
if scoped, ok := r.scopedSessions(); ok {
|
||||
if rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx); ok {
|
||||
scoped.SetReceivesUpdatesForAuthKey(rawAuthKeyID, sessionID, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
r.deps.Sessions.SetReceivesUpdates(sessionID, true)
|
||||
r.deps.Sessions.SetReceivesUpdatesForAuthKey(rawAuthKeyIDForOrigin(ctx), sessionID, true)
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue