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() { roomCtx := appcode.WithContext(ctx, roomRef.AppCode) lease, owned, err := s.loadedRoomLease(roomCtx, roomRef, now) if err != nil { return err } if !owned { continue } roomCell := s.loadCell(roomCtx, roomRef.RoomID) if roomCell == nil { 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) { owned, err = s.verifyLoadedRoomLease(roomCtx, roomRef, lease, s.clock.Now()) if err != nil { return err } if !owned { break } 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 } 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 == "" { continue } if seat.PublishState == state.MicPublishPending && seat.PublishDeadlineMS > 0 && seat.PublishDeadlineMS <= nowMs { // 返回值拷贝,后续 MicDown 还会用 mic_session_id 在当前 Cell 状态中二次校验。 seats = append(seats, seat) } } return seats }