owpengram-server/internal/rpc/groupcall_sweep_dispatcher.go

98 lines
3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"time"
"go.uber.org/zap"
"telesrv/internal/domain"
"telesrv/internal/sfu"
)
// GroupCallSweepDispatcher 清理群通话幽灵参与者kill 进程/断电后无人发 leave
//
// ⚠ P0-2 活性契约:客户端只在 Connecting 态发 checkGroupCall媒体连通后心跳停止
// 单凭 last_check_date 判死会把所有连上媒体的健康参与者踢光。约定的判据是
// 「max(心跳, 媒体面活性)」M0SFU disabled客户端恒 Connecting、4s 心跳不断,
// 纯水位成立M1 起 SFU 必须对媒体面存活ICE consent/近期 SRTP 收包)的 endpoint
// 周期性刷新 last_check_dateliveness reporter使单一水位同时承载两路活性。
type GroupCallSweepDispatcher struct {
router *Router
log *zap.Logger
interval time.Duration
checkTTL time.Duration
batch int
}
func NewGroupCallSweepDispatcher(router *Router, log *zap.Logger, interval, checkTTL time.Duration) *GroupCallSweepDispatcher {
if log == nil {
log = zap.NewNop()
}
if interval <= 0 {
interval = 10 * time.Second
}
if checkTTL <= 0 {
checkTTL = 45 * time.Second
}
return &GroupCallSweepDispatcher{router: router, log: log, interval: interval, checkTTL: checkTTL, batch: 100}
}
func (d *GroupCallSweepDispatcher) Run(ctx context.Context) {
if d == nil || d.router == nil {
return
}
ticker := time.NewTicker(d.interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
d.DispatchOnce(ctx)
}
}
}
func (d *GroupCallSweepDispatcher) DispatchOnce(ctx context.Context) {
if d == nil || d.router == nil || d.router.deps.GroupCalls == nil {
return
}
now := d.router.clock.Now()
cutoff := int(now.Add(-d.checkTTL).Unix())
muts, err := d.router.deps.GroupCalls.SweepStale(ctx, cutoff, int(now.Unix()), d.batch)
if err != nil {
d.log.Warn("sweep group call participants", zap.Error(err))
return
}
for _, mut := range muts {
if d.router.deps.SFU != nil {
_ = d.router.deps.SFU.Leave(ctx, mut.Call.ID, mut.Participant.UserID, sfu.EndpointMain)
}
if mut.Call.Conference() {
d.router.groupCallMutationFanout(ctx, domain.Channel{}, mut)
d.log.Info("conference call participant swept",
zap.Int64("call_id", mut.Call.ID),
zap.Int64("user_id", mut.Participant.UserID),
zap.String("state", string(mut.Call.State)))
continue
}
channel, err := d.router.channelForGroupCall(ctx, mut.Call)
if err != nil {
continue
}
d.router.groupCallMutationFanout(ctx, channel, mut)
d.log.Info("group call participant swept",
zap.Int64("call_id", mut.Call.ID),
zap.Int64("user_id", mut.Participant.UserID))
}
}
// channelForGroupCall 取通话所属频道行(推送 hydration 用)。
func (r *Router) channelForGroupCall(ctx context.Context, call domain.GroupCall) (domain.Channel, error) {
view, err := r.deps.Channels.GetChannel(ctx, call.CreatorUserID, call.ChannelID)
if err != nil {
return domain.Channel{}, err
}
return view.Channel, nil
}