feat(gateway): 忙时并行帧层(turn_begin + steer_ack.turnId + interrupt.turnId)
新下行帧 turn_begin:编排器每启动一路恰好一帧(单路 kind:"turn" / 并行路 "parallel"),
data{kind, messageIds, leg, activeTurns};角色门 fail-closed 仅 owner+admin,未进 frameReachesAll。
steer_ack 增帧壳 turnId(与该路 turn_begin 同源)—— 并行下没有它配不上正确的 turn.end。
上行 interrupt.turnId(仅 scope:"current" 生效):精确停某一路;目标不存在 ⇒ 定向
{type:"error",data:{code:"bad_request"}},不广播、不动任何会话;省略 turnId = v0.65 原口径(全部在途路)。
帧序按实现:session_busy / pending_drain → message_persisted(user) → turn_begin
(规范草案 §2.9 的时序图需按实现更正:messageIds 必须是已落库的权威 id,故只能在落库之后)。
测试:编排器侧 + 真 WS/JWT 端到端;变异 5 条(每路一帧 / 帧壳 turnId / 目标化打断 / 取消筛选)。
Docs 待升 v0.66(由监工登记)。
This commit is contained in:
1 parent
e5cc9c6054
commit
c0dc435770
13 files changed
+800
-45
No files matched your search
@@ -56,6 +56,33 @@ func (o *Orchestrator) InterruptScope(sessionID string, scope InterruptScope) in
|
||||
return o.interruptLocked(sessionID, o.states[sessionID], scope)
|
||||
}
|
||||
|
||||
// InterruptTurn 只取消 `turnId` 指定的那**一路**(忙时并行规范草案 §2.3:
|
||||
// `interrupt{scope:"current", turnId}`),其余在途路与待处理队列一概不受影响 ——
|
||||
// 每路各有自己的取消链,因此停一路绝不牵动另一路已发起的工具(I3′)。
|
||||
//
|
||||
// 返回 false 表示该会话此刻没有这条路(不存在 / 已收尾 / 已被取消)。调用方据此
|
||||
// 定向回一条显式错误帧,**绝不**静默按「全部在途路」处理:把「只停这一路」悄悄
|
||||
// 执行成「停掉所有路」,正是这类定向能力最不该有的失败方式。
|
||||
//
|
||||
// 它与 InterruptScope(Current) 的差别只有「目标集」:没有目标就是每一路(现状),
|
||||
// 有目标就只取消它。帧与队列口径完全一致(dropped 恒为 0、mode 如实报告当时的
|
||||
// 忙碌通道、被取消的路仍在 st.legs 里收尾到自然结算)。
|
||||
func (o *Orchestrator) InterruptTurn(sessionID, turnID string) bool {
|
||||
if turnID == "" {
|
||||
return false
|
||||
}
|
||||
sessionID = o.normalize(sessionID)
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
st := o.states[sessionID]
|
||||
if o.legByTurnIDLocked(st, turnID) == nil {
|
||||
return false
|
||||
}
|
||||
canceled := o.cancelLegsLocked(st, turnID)
|
||||
o.emitState(sessionID, EventSessionInterrupt, interruptPayload(sessionID, st.mode, 0, canceled, len(st.legs)))
|
||||
return true
|
||||
}
|
||||
|
||||
// InterruptAll 取消每一个在飞会话并清空所有待处理队列。它在网关关闭时使用,
|
||||
// 使没有后台回合比 hub 活得更久。它返回被丢弃的待处理回合总数。
|
||||
func (o *Orchestrator) InterruptAll() int {
|
||||
@@ -73,15 +100,16 @@ func (o *Orchestrator) InterruptAll() int {
|
||||
// Current 只取消在途路,队列与路登记原样留下,使被取消回合的结算仍会续跑队列。
|
||||
// 它返回真的被丢弃的待处理回合数(Current 恒为 0)。
|
||||
//
|
||||
// 多路口径(design/15 §2.5.4):无论哪个 scope,被取消的都是**当前全部在途路**
|
||||
// —— 省略 turnId 的 scope 就是「作用于每一路」的意思(精确停某一路是后续切片的
|
||||
// `interrupt.turnId`,本切片不做)。解除在途登记的方式与旧口径的 `st.gen++` 等价:
|
||||
// 把路从 st.legs 里摘掉,被取消路的迟到结算因此找不到自己的路、成为空操作。
|
||||
// 多路口径(design/15 §2.5.4):这里取消的是**当前全部在途路**(目标传空串)
|
||||
// —— 省略 turnId 的 scope 就是「作用于每一路」的意思;「只停某一路」走
|
||||
// InterruptTurn(同一套取消链,只是带上目标)。解除在途登记的方式与旧口径的
|
||||
// `st.gen++` 等价:把路从 st.legs 里摘掉,被取消路的迟到结算因此找不到自己的
|
||||
// 路、成为空操作。
|
||||
func (o *Orchestrator) interruptLocked(sessionID string, st *sessionState, scope InterruptScope) int {
|
||||
if st == nil || st.mode == ModeIdle {
|
||||
return 0
|
||||
}
|
||||
canceled := o.cancelLegsLocked(st)
|
||||
canceled := o.cancelLegsLocked(st, "")
|
||||
if scope == InterruptScopeCurrent {
|
||||
// 只取消在途路:不碰 pending、不解除路的登记(它们仍在收尾)。mode 如实
|
||||
// 报告为当时的忙碌通道(回合仍在收尾、队列还在续跑),而不是 idle。
|
||||
|
||||
Reference in new issue
Block a user