2026-07-07 18:28:50 +08:00

124 lines
3.6 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 service
import (
"context"
"time"
"hyapp/pkg/appcode"
"hyapp/pkg/idgen"
"hyapp/services/room-service/internal/room/command"
"hyapp/services/room-service/internal/room/state"
)
const micDownReasonPublishTimeout = "publish_timeout"
// RunMicPublishTimeoutWorker 周期性释放超过 deadline 仍未确认 RTC 发流的麦位。
func (s *Service) RunMicPublishTimeoutWorker(ctx context.Context, interval time.Duration) {
if interval <= 0 || s.micPublishTimeout <= 0 {
// 非正数表示显式关闭,便于测试或临时止血。
return
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
_ = s.SweepMicPublishTimeouts(ctx)
}
}
}
// SweepMicPublishTimeouts 扫描本节点已装载 Room Cell自动下掉未确认发流的 pending_publish 麦位。
func (s *Service) SweepMicPublishTimeouts(ctx context.Context) error {
now := s.clock.Now()
nowMs := now.UnixMilli()
for _, roomRef := range s.loadedRoomRefs() {
// 后台扫描只处理本进程已装载的 Cell未装载房间由下一次用户命令恢复后再处理。
roomCtx := appcode.WithContext(ctx, roomRef.AppCode)
lease, owned, err := s.loadedRoomLease(roomCtx, roomRef, now)
if err != nil {
return err
}
if !owned {
// lease 已被其他节点持有时,旧 owner 的内存状态不能再驱动下麦命令。
continue
}
roomCell := s.loadCellForLease(roomCtx, roomRef.RoomID, lease)
if roomCell == nil {
// loadedRoomRefs 和 loadCell 之间允许并发变化,缺失时跳过即可。
continue
}
// 先在命令边界读取快照,再根据快照筛出候选超时麦位。
currentState, _, err := roomCell.Snapshot(roomCtx)
if err != nil {
return err
}
owned, err = s.verifyLoadedRoomLease(roomCtx, roomRef, lease, s.clock.Now())
if err != nil {
return err
}
if !owned {
continue
}
for _, seat := range pendingPublishTimedOutSeats(currentState, nowMs) {
// 每个候选执行前再次校验 lease避免长批次期间 owner 已切换还继续写旧状态。
owned, err = s.verifyLoadedRoomLease(roomCtx, roomRef, lease, s.clock.Now())
if err != nil {
return err
}
if !owned {
break
}
// 通过标准 MicDown 命令链路释放麦位,复用 command log、outbox、快照和幂等保护。
cmd := command.MicDown{
Base: command.Base{
RequestID: idgen.New("req_mic_publish_timeout"),
CommandID: idgen.New("cmd_mic_publish_timeout"),
ActorID: seat.UserID,
Room: roomRef.RoomID,
GatewayNodeID: s.nodeID,
SessionID: "mic-publish-timeout",
SentAtMS: nowMs,
},
TargetUserID: seat.UserID,
MicSessionID: seat.MicSessionID,
Reason: micDownReasonPublishTimeout,
}
if _, err := s.micDown(roomCtx, cmd); err != nil {
return err
}
}
}
return nil
}
// pendingPublishTimedOutSeats 返回已经超过确认 deadline 的 pending_publish 麦位快照。
func pendingPublishTimedOutSeats(current *state.RoomState, nowMs int64) []state.MicSeat {
if current == nil {
return nil
}
seats := make([]state.MicSeat, 0)
for _, seat := range current.MicSeats {
if seat.UserID <= 0 || seat.MicSessionID == "" {
// 空麦位或没有 mic_session 的历史状态不属于自动下麦目标。
continue
}
if seat.PublishState == state.MicPublishPending && seat.PublishDeadlineMS > 0 && seat.PublishDeadlineMS <= nowMs {
// 返回值拷贝,后续 MicDown 还会用 mic_session_id 在当前 Cell 状态中二次校验。
seats = append(seats, seat)
}
}
return seats
}