package channelbridge // 本文件承载车道的**单消费者**:一个会话一个 worker,串行跑完队列里的每一条。 // // 「回合在跑」在结构上就等于「worker 正忙」—— M23 的回合中续跑因此不需要任何 // turn-in-flight 标志:回合结束回到循环头,takeAll 自然把这段期间攒下的消息整片取出。 // // direct 车道(主人私聊)在这里按 `l.batcher == nil` 分派到 runDirectLaneItem: // 到达即投递、一条一条跑,**没有**任何窗口等待。 import ( "context" "time" ) // laneWorker 是某会话车道的唯一执行者:串行跑完每一条,队列空了就把车道交还注册表 // (空闲车道不占内存,也不会把「这个人再说话」变慢)。 // // **回合中续跑(⑤)就落在这里**:不存在任何 "turn in flight" 标志 —— 「回合在跑」 // 在结构上就等于「worker 正忙」,跑回合期间新消息落进同一个队列,回合结束回到循环头, // takeAll 自然把这段期间攒下的消息整片取出 ⇒ **一轮续跑**(不是 N 轮,也不是 0 条)。 func (b *Bridge) laneWorker(ctx context.Context, l *lane) { defer b.wg.Done() for { // 桥已停:队列里还没发车的条目**如实登记**(绝不静默消失),然后退休。 if ctx.Err() != nil { b.abandonLaneAtStop(l) b.retireLane(l) return } if item, ok := b.nextLaneItem(l); ok { b.dispatchLaneItem(ctx, l, item) continue } // 队列空了才谈退休:摘表与入队用同一把锁,因此「摘下」与「刚有人塞进来」不会 // 交错成条目丢失(见 retireLaneIfIdle 的说明)。 if b.retireLaneIfIdle(l) { return } select { case <-ctx.Done(): // 桥停了:把车道摘掉,未处理的条目随进程取消一起作废(Stop 的语义)。 b.retireLane(l) return case item := <-l.ch: b.dispatchLaneItem(ctx, l, item) } } } // dispatchLaneItem 按**车道类型**分派:direct(主人私聊)一条一条跑;batch 走窗口 // 引擎(见 coalesce_lane.go 的 runBatchLaneItem)。 // // 分派依据是 `l.batcher == nil` —— 一个**结构**判据,不是配置开关:direct 车道的 // 类型里根本没有 batcher 字段值,窗口代码对它不可达。 func (b *Bridge) dispatchLaneItem(ctx context.Context, l *lane, item laneItem) { if l.batcher == nil { b.runDirectLaneItem(ctx, l, item) return } b.runBatchLaneItem(ctx, l, item) } // runDirectLaneItem 跑 direct 车道(= 主人私聊)的一条:先取全局回合槽(**在线程池里 // 等,不在读循环里等**),再过陈旧度闸,然后走逐条闸门 + 单条回合。**没有任何等待窗口**。 func (b *Bridge) runDirectLaneItem(ctx context.Context, l *lane, item laneItem) { if !b.acquireTurn(ctx) { return } defer b.releaseTurn() waited := b.now().Sub(item.arrivedAt) if b.staleInbound(item, waited) { return } b.logLaneStart(item, waited, 1) p, ok := b.prepareInbound(ctx, item) if !ok { l.lastTurnEnd = b.now() return } b.runBatch(ctx, []preparedInbound{p}) l.lastTurnEnd = b.now() } // acquireTurn 取一个全局回合槽(maxConcurrent 个)。ctx 取消时返回 false:桥停了 // 就不再开新回合(carried 条目由 Stop 的诚实登记负责,见 coalesce_lane.go)。 func (b *Bridge) acquireTurn(ctx context.Context) bool { select { case b.sem <- struct{}{}: return true case <-ctx.Done(): return false } } // releaseTurn 归还一个全局回合槽。 func (b *Bridge) releaseTurn() { <-b.sem } // logLaneStart 记「这一批等了多久才开始跑」—— 「她为什么回得晚」在生产日志里最直接 // 的那个答案。与它同时出现在日志里的 `msg="inbound frame"`(适配器的入站帧 DEBUG) // 则说明读循环在等的同时一直在读:两行合起来就是「回合没有堵住心跳」的证据。 func (b *Bridge) logLaneStart(item laneItem, waited time.Duration, messages int) { b.lanes.log.Debug("lane turn started", "waited", waited.String(), "messages", messages, "platform", item.msg.Platform, "channel", item.msg.ChannelID, "sender", item.msg.SenderID, "group", item.msg.GroupID) }