fix(flow): 时间轴两处诚实性缺陷(X1)
① round 号跨重启被复用 ⇒ 时间轴把多个真实轮次并成一块(真机实测 web 的 r:1 挤了 4 路、 startedAt 跨 13:52→21:05)。修法:发号前从落盘播种(MaxRound,json_extract + json_valid 挡坏行), 每会话每进程只播一次、失败只 WARN 且不重试、并发首轮共享一次读;接口形状零变化 ② truncated 一个字段兼两个意思 ⇒ 一页没拉完时前端会说「更早的轮次已过保留期」(假话)。 新增 retentionBoundaryReached(= 保留期启用 且 已翻到底 且 现存最早一轮 round>1 ⇒ 确有轮次被清扫), truncated 语义一字未改;盲区如实登记(整段被清扫会说少、最早一轮落盘失败可能多说) 3 个变异自检(不落账/floor>0/去掉 hasMore 守卫)
This commit is contained in:
1 parent
b36798265a
commit
2bf3b536dd
9 files changed
+820
-12
No files matched your search
@@ -82,9 +82,13 @@ type FlowPublisher struct {
|
||||
// (`{会话}-{序号}[#{路}]`)。用序号而不是轮次号做 id 的第二个分量,是为了让
|
||||
// 「同一轮里的第二条路」也拿到一个全新的 id —— 否则两路会撞同一个
|
||||
// `flow_graphs.turn_id`(唯一键),后收尾的那一路把前一路整行顶掉。
|
||||
//
|
||||
// **跨重启**:本进程第一次给某会话发号之前,`ensureRoundSeeded` 会把它播到该会话
|
||||
// **已落盘**的最大轮次号之上(见 flow_roundseed.go),因此重启不会重用轮次号。
|
||||
seq map[string]int
|
||||
// rounds 是每会话当前的**轮次号**(= 该轮第一条路的建图序号)。同一轮的各条路
|
||||
// 共享它,客户端据此只保留最新一轮的泳道。
|
||||
// 共享它,客户端据此只保留最新一轮的泳道。它**跨重启单调**(同上:从落盘侧播种),
|
||||
// 因此时间轴按它分组时不会把重启前后的两轮并成一块。
|
||||
rounds map[string]int
|
||||
// roundOf 是「归属桶 → 它属于哪一轮」。
|
||||
roundOf map[string]int
|
||||
@@ -92,6 +96,10 @@ type FlowPublisher struct {
|
||||
// (集合为空),因此它是轮次号推进的唯一依据,也是内存不随路数无界增长的
|
||||
// 依据(新一轮起时丢掉更早轮次的桶)。
|
||||
inflight map[string]map[string]bool
|
||||
// seedDone 是「该会话的播种动作已完成」的标记(成功或放弃,见 flow_roundseed.go);
|
||||
// seeding 是正在播种的会话 → 完成信号,使同一会话的并发首轮等这一次播种。
|
||||
seedDone map[string]bool
|
||||
seeding map[string]chan struct{}
|
||||
// record 是每轮收尾的落盘出口(design/19 §4 P3;切片 F2)。nil = 不落盘
|
||||
// (保持 F1 之前的纯内存行为,且测试可只测发布)。
|
||||
record RoundRecorder
|
||||
@@ -114,6 +122,8 @@ func NewFlowPublisher(publish func(subject string, payload any), allow func(sess
|
||||
rounds: make(map[string]int),
|
||||
roundOf: make(map[string]int),
|
||||
inflight: make(map[string]map[string]bool),
|
||||
seedDone: make(map[string]bool),
|
||||
seeding: make(map[string]chan struct{}),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
@@ -161,11 +171,15 @@ func (p *FlowPublisher) StartRoundWithTurnID(ctx context.Context, owner, inputTe
|
||||
// 轮次号只在**上一轮的路全部收尾**之后推进(inflight 为空),因此「同一条消息
|
||||
// 引出的两路」共享同一个轮次号;新一轮开始时丢掉更早轮次的桶,内存因此不随路数
|
||||
// 无界增长(图的历史在落盘里,见 flowgraph 包)。
|
||||
//
|
||||
// 发号之前先 `ensureRoundSeeded`:本进程第一次给该会话发号时从落盘侧播种,使轮次号
|
||||
// **跨重启单调**(否则重启后从 1 重来,会把库里的旧轮次并进同一块,见 flow_roundseed.go)。
|
||||
func (p *FlowPublisher) startRound(owner, turnID, inputText string) {
|
||||
session, _ := splitFlowOwner(owner)
|
||||
if session == "" || owner == "" || !p.allowed(session) {
|
||||
return
|
||||
}
|
||||
p.ensureRoundSeeded(session)
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
round := p.beginRoundLocked(session, owner)
|
||||
@@ -184,6 +198,7 @@ func (p *FlowPublisher) startRound(owner, turnID, inputText string) {
|
||||
}
|
||||
|
||||
// beginRoundLocked 分配(或沿用)本轮次号并清掉更早轮次的桶。调用方必须持有 p.mu。
|
||||
// 计数器由 `ensureRoundSeeded` 在发号之前播种(跨重启单调,见 flow_roundseed.go)。
|
||||
func (p *FlowPublisher) beginRoundLocked(session, owner string) int {
|
||||
p.seq[session]++
|
||||
if len(p.inflight[session]) == 0 {
|
||||
|
||||
Reference in new issue
Block a user