feat(gateway,loop): 忙时并行回合基础管线(I1′ 有条件放宽)
- turnId 由编排器生成、loop Run 复用(硬前提:否则 turn_begin 与流帧各铸一个,客户端两级分桶立即失配) - StateSink 增带 turnId 的变体(未接线时退化为现签名,不丢状态帧) - 上行 chat.parallel(WS chat 帧 + REST 三入口同形;省略 = 逐字不变) - 单会话额度默认 2 / 硬上限 3 / <2 关闭;额度满或未声明 ⇒ 落回既有排队语义 - I1′:checkpoint 归属、历史配对、忙态计数三处从会话级单例升为路级 - session_idle 收紧为「所有在途路收尾且 pending 空」(仅并行时生效;单路逐字不变) - 预留接口(形状定死、无实现无接线):tool/legfile.go 的 FileClaimer / ErrFileBusy / WithLegID - 顺手修一个真 bug:并行路 ctx 派生自提交请求作用域,REST 同步入口一写响应就把它连坐杀死(改 context.WithoutCancel 派生) - 23 个新用例(含 -race -count=3);变异 6 条(turnId 复用 / idle 时点 / 并行升级 / checkpoint 键 / 编排器交 id / pending_ack reason) - 契约变更点 10 条待监工升 v0.65(本人未改 Docs/**)
This commit is contained in:
1 parent
7d2ff74cfc
commit
89e13cf53d
23 files changed
+1921
-275
No files matched your search
@@ -13,17 +13,17 @@ package gateway
|
||||
type InterruptScope string
|
||||
|
||||
const (
|
||||
// InterruptScopeAll 取消在飞回合并清空待处理队列。它是省略 scope 时的
|
||||
// InterruptScopeAll 取消全部在途路并清空待处理队列。它是省略 scope 时的
|
||||
// 默认口径,与历史行为逐字一致。
|
||||
InterruptScopeAll InterruptScope = "all"
|
||||
// InterruptScopeCurrent 只取消在飞回合,**保留**待处理队列:队列里的消息
|
||||
// 按编排器正常的续跑规则继续执行,一条都不丢。
|
||||
// InterruptScopeCurrent 只取消在途路(多路时为**每一路**,见下),**保留**
|
||||
// 待处理队列:队列里的消息按编排器正常的续跑规则继续执行,一条都不丢。
|
||||
InterruptScopeCurrent InterruptScope = "current"
|
||||
)
|
||||
|
||||
// Interrupt 取消在飞回合、清空待处理队列并把会话回到空闲(design/15 §3,
|
||||
// Interrupt 取消**全部**在途路、清空待处理队列并把会话回到空闲(design/15 §3,
|
||||
// 「停下/算了/换个话题」路径)。它是忙碌会话不必排空队列就能复位的唯一方式;
|
||||
// 运行中 goroutine 的完成变成陈旧代次,不续跑任何东西。它返回被丢弃的
|
||||
// 运行中 goroutine 的完成变成失效路,不续跑任何东西。它返回被丢弃的
|
||||
// 待处理回合数。
|
||||
//
|
||||
// 它把会话立即报成空闲并立即接受下一条消息(design/15 §3 的「立即
|
||||
@@ -36,11 +36,12 @@ func (o *Orchestrator) Interrupt(sessionID string) int {
|
||||
// InterruptScope 按 scope 中断一个会话(协同项 A2),返回真的被丢弃的
|
||||
// 待处理回合数:
|
||||
//
|
||||
// - InterruptScopeAll:取消在飞回合、清空待处理队列并把会话回到空闲
|
||||
// - InterruptScopeAll:取消全部在途路、清空待处理队列并把会话回到空闲
|
||||
// —— 省略 scope 时的历史语义,逐字不变;
|
||||
// - InterruptScopeCurrent:只取消在飞回合,**保留**待处理队列。它不递增
|
||||
// 代次,因此被取消回合的结算(completeTurn)仍属当代,会按正常续跑
|
||||
// 规则弹出队列里的下一条消息、带上会话尾部上下文继续跑(design/15
|
||||
// - InterruptScopeCurrent:只取消在途路(多路时每一路都取消,因为省略
|
||||
// turnId 的 scope 就是「作用于每一路」——design/15 §2.5.4),**保留**待处理
|
||||
// 队列。它不解除路的登记,因此被取消回合的结算(completeTurn)仍会按正常
|
||||
// 续跑规则弹出队列里的下一条消息、带上会话尾部上下文继续跑(design/15
|
||||
// §2.2),返回 0 —— 真的没有消息被丢弃。
|
||||
//
|
||||
// 空字符串与省略同义(都是现状语义),使老客户端只发 {sessionId} 的
|
||||
@@ -68,27 +69,43 @@ func (o *Orchestrator) InterruptAll() int {
|
||||
}
|
||||
|
||||
// interruptLocked 在状态锁下执行一次中断。scope 决定待处理队列的去留
|
||||
// (见 InterruptScope):All 清队列、递增代次并把会话回到空闲;Current
|
||||
// 只取消在飞回合,队列与代次原样留下,使被取消回合的结算仍会续跑队列。
|
||||
// (见 InterruptScope):All 清队列、使全部在途路失效并把会话回到空闲;
|
||||
// Current 只取消在途路,队列与路登记原样留下,使被取消回合的结算仍会续跑队列。
|
||||
// 它返回真的被丢弃的待处理回合数(Current 恒为 0)。
|
||||
//
|
||||
// 多路口径(design/15 §2.5.4):无论哪个 scope,被取消的都是**当前全部在途路**
|
||||
// —— 省略 turnId 的 scope 就是「作用于每一路」的意思(精确停某一路是后续切片的
|
||||
// `interrupt.turnId`,本切片不做)。解除在途登记的方式与旧口径的 `st.gen++` 等价:
|
||||
// 把路从 st.legs 里摘掉,被取消路的迟到结算因此找不到自己的路、成为空操作。
|
||||
func (o *Orchestrator) interruptLocked(sessionID string, st *sessionState, scope InterruptScope) int {
|
||||
if st == nil || st.mode == ModeIdle {
|
||||
return 0
|
||||
}
|
||||
if st.cancel != nil {
|
||||
st.cancel()
|
||||
}
|
||||
canceled := o.cancelLegsLocked(st)
|
||||
if scope == InterruptScopeCurrent {
|
||||
// 只取消在途回合:不碰 pending、不递增代次。mode 如实报告为
|
||||
// 当时的忙碌通道(回合仍在收尾、队列还在续跑),而不是 idle。
|
||||
o.emitState(sessionID, EventSessionInterrupt, map[string]any{"sessionId": sessionID, "mode": st.mode, "dropped": 0})
|
||||
// 只取消在途路:不碰 pending、不解除路的登记(它们仍在收尾)。mode 如实
|
||||
// 报告为当时的忙碌通道(回合仍在收尾、队列还在续跑),而不是 idle。
|
||||
o.emitState(sessionID, EventSessionInterrupt, interruptPayload(sessionID, st.mode, 0, canceled, len(st.legs)))
|
||||
return 0
|
||||
}
|
||||
dropped := len(st.pending)
|
||||
st.pending = nil
|
||||
st.gen++
|
||||
st.legs = nil
|
||||
st.mode = ModeIdle
|
||||
st.cancel = nil
|
||||
o.emitState(sessionID, EventSessionInterrupt, map[string]any{"sessionId": sessionID, "mode": ModeIdle, "dropped": dropped})
|
||||
o.emitState(sessionID, EventSessionInterrupt, interruptPayload(sessionID, ModeIdle, dropped, canceled, 0))
|
||||
return dropped
|
||||
}
|
||||
|
||||
// interruptPayload 组装中断帧的载荷(design/15 §2.6):既有键(sessionId / mode /
|
||||
// dropped)一个不改,新增两个**计数**键 —— cancelledTurns(本次真的取消了几路)
|
||||
// 与 activeTurns(清点之后还在途的路,scope=all 恒为 0)。两者都不含内容,
|
||||
// 因此该帧仍在「状态机信号」白名单内。
|
||||
func interruptPayload(sessionID string, mode Mode, dropped, cancelledTurns, activeTurns int) map[string]any {
|
||||
return map[string]any{
|
||||
"sessionId": sessionID,
|
||||
"mode": mode,
|
||||
"dropped": dropped,
|
||||
"cancelledTurns": cancelledTurns,
|
||||
"activeTurns": activeTurns,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user