From 83a992b4cc004044771750e9c62726fd478442ec Mon Sep 17 00:00:00 2001 From: onysd Date: Mon, 20 Jul 2026 17:22:53 +0300 Subject: [PATCH] fix --- internal/rpc/frozen_gate_test.go | 5 +++-- internal/rpc/phone_push.go | 26 +++++++++++++++----------- internal/rpc/phone_rpc_test.go | 7 +++++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/internal/rpc/frozen_gate_test.go b/internal/rpc/frozen_gate_test.go index 72c03268..95fc1996 100644 --- a/internal/rpc/frozen_gate_test.go +++ b/internal/rpc/frozen_gate_test.go @@ -238,8 +238,9 @@ func TestFrozenCalleeCompletesPrivateCallLifecycle(t *testing.T) { if value, ok := dispatchCanonicalValue(signaled).(bool); !ok || !value { t.Fatalf("sendSignalingData = %#v, want true", dispatchCanonicalValue(signaled)) } - if pushes := f.sessions.records(); len(pushes) != 1 || pushes[0].targetSession != phoneCallerSession { - t.Fatalf("signaling pushes = %+v, want caller device", pushes) + // 信令走 user 级 transient 扇出到对端全部 ready 连接、排除发送方 session。 + if pushes := f.sessions.records(); len(pushes) != 1 || pushes[0].userID != f.caller.ID || pushes[0].excludeSession != phoneCalleeSession { + t.Fatalf("signaling pushes = %+v, want transient fanout to caller excluding callee session", pushes) } f.sessions.reset() diff --git a/internal/rpc/phone_push.go b/internal/rpc/phone_push.go index 45f03e75..07af980f 100644 --- a/internal/rpc/phone_push.go +++ b/internal/rpc/phone_push.go @@ -3,7 +3,6 @@ package rpc import ( "context" - "github.com/iamxvbaba/td/proto" "github.com/iamxvbaba/td/tg" "telesrv/internal/domain" @@ -59,10 +58,20 @@ func (r *Router) pushPhoneCallDiscardedBoth(ctx context.Context, call domain.Pho r.pushPhoneCall(ctx, call.ParticipantID, call, "phone call discarded") } -// pushPhoneSignalingData 把信令字节透传给对端。优先走设备锚点定向推送 -// (requestCall/acceptCall 受理设备),锚点失效(设备重连换 session)则回退 -// user 级扇出——非参与设备按 phone_call_id 不匹配静默丢弃(TDesktop -// handleSignalingData 行为),扇出无害。 +// pushPhoneSignalingData 把信令字节透传给对端。 +// +// ⚠ 信令是【瞬时】数据(有效期秒级:tgcalls 的 offer/answer/candidate 交换)。 +// 绝不能定向推给单个「受理设备」的 session 锚点——本部署里被叫真机把 dc 1..5 别名到 +// 同一服务器,故一台真机对本服务器有多条连接/session,而它【接收 update 的那条】 +// 未必就是承载 acceptCall RPC 的那条。若锚点指向一条尚未 updates-ready 的 session, +// PushToSessionForAuthKey 会把信令【塞进 pending 队列并返回 nil】(不是错误),于是 +// 旧代码误以为已投递、不再回退——offer 被积压在死队列里、被叫的 tgcalls 永远收不到, +// 通话一接就断(且只在被叫是多连接的一侧发生,故 A→B 断、B→A 通的方向不对称)。 +// +// 正确做法:按 user 级【transient】扇出到该用户【全部 updates-ready 的连接】——未就绪 +// 的连接直接跳过、不入 pending(瞬时信令 getDifference 也补不回,积压有害无益)。 +// 运行 tgcalls 的那条 ready 连接必收到;非参与设备按 phone_call_id 不匹配静默丢弃 +// (TDesktop/DrKLO handleSignalingData 行为),扇出无害。device 锚点已不再使用。 func (r *Router) pushPhoneSignalingData(ctx context.Context, targetUserID int64, device domain.SessionRef, callID int64, data []byte) { upd := &tg.Updates{ Updates: []tg.UpdateClass{&tg.UpdatePhoneCallSignalingData{ @@ -74,10 +83,5 @@ func (r *Router) pushPhoneSignalingData(ctx context.Context, targetUserID int64, Date: int(r.clock.Now().Unix()), Seq: 0, } - 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) + r.pushUserMessageTransient(ctx, targetUserID, "phone call signaling", upd) } diff --git a/internal/rpc/phone_rpc_test.go b/internal/rpc/phone_rpc_test.go index b114d683..3c20277a 100644 --- a/internal/rpc/phone_rpc_test.go +++ b/internal/rpc/phone_rpc_test.go @@ -317,8 +317,11 @@ func TestPhoneCallRPCHappyPath(t *testing.T) { t.Fatalf("sendSignalingData = %v err=%v", okSig, err) } pushes = f.sessions.records() - if len(pushes) != 1 || pushes[0].targetSession != phoneCalleeSession { - t.Fatalf("signaling pushes = %+v, want one to callee session", pushes) + // 信令按 user 级 transient 扇出到对端【全部 ready 连接】,排除发送方 session; + // 不再定向到单个「受理设备」锚点(那会在被叫多连接、锚点未 ready 时把 offer 塞进 + // 死队列、通话一接就断)。See pushPhoneSignalingData / memory: call-signaling-*. + if len(pushes) != 1 || pushes[0].userID != f.callee.ID || pushes[0].excludeSession != phoneCallerSession { + t.Fatalf("signaling pushes = %+v, want transient fanout to callee excluding caller session", pushes) } sigUpdates := pushes[0].msg.(*tg.Updates) sig, ok := sigUpdates.Updates[0].(*tg.UpdatePhoneCallSignalingData)