Commit Graph

2 Commits

Author SHA1 Message Date
8884cf9962 fix(io): 并发判据自带串话 —— 32 个 goroutine 抢 26 个 key
## 缺陷(既有,实测在本次改动之前的基线上同样复现)

`TestConsumeToolBlocksFor_ConcurrentNoCrossTalk` 约 1/8 概率假红:

    .go:105: call E 期望 [block-4-E],实际 []

根因在判据**自己**:

    const n = 32
    id := string(rune('A' + i%26))    // ← 字母只有 26 个

i=0 与 i=26 都得到 'A',i=1 与 i=27 都得 'B' …… 共 6 个 id 各被两个
goroutine 共用。两者共享同一个 key:后 Set 覆盖先 Set 的、先 Consume
拿到空或别人的值。

所以它想验证"不同 key 不串话",却让多个 goroutine 抢同一个 key ——
**判据违反了它自己想验证的前提**。这不是 `SetToolBlocksFor` /
`ConsumeToolBlocksFor` 的并发缺陷(共用 key 本就该覆盖)。

## 修法

    id := "call-" + strconv.Itoa(i)      // 唯一

实测:修改前 12 次内命中 1 次;修改后连跑 20 次 0 失败。

## 补两条判据把前提本身钉住

  TestConsumeToolBlocksFor_KeysAreIsolated
      一次性写 64 个**互不相同**的 key,逐个读回必须各得其所。
      不涉及并发 ⇒ 失败一定是真缺陷,不会偶发。

  TestConsumeToolBlocksFor_ConsumeIsOneShot
      读一次后清空(block 是一次性的)。
      变异:删掉 `delete(m.toolPendingBlocks, callID)` ⇒ 实测红:
          ★ 二次消费应得空(block 是一次性的),实际 [b]

## 为什么这条值得单独修

一个 1/8 概率假红的判据,实际效果等于**没有判据**:
它在 CI 里红了几次之后,所有人都会学会"重跑一次"。
而这正是本仓"长红 ⇒ 整道门失效"教训的一个变体。

## 验证

    go build ./...            PASS
    go vet ./...              PASS
    gofmt -l                  干净
    go test ./... -count=1    65 包全过,0 FAIL
    该判据连跑 20 次          0 失败(修改前 12 次中 1 次)
2026-11-04 19:24:22 +08:00
3d80e924e5 feat(io): 多模态块改 per-call 归档,为并行执行铺路(阶段 2b)
问题:ConsumeToolBlocks 此前是 **IOManager 级单队列**(取走即清空),
没有 call_id 维度。并行化后同批多个工具各自注入媒体时,后执行的
Consume 会**抢走**前一个的块 ⇒ 媒体挂到错误的 tool 消息上。而
task.go 里「媒体必须走 user message 且紧跟自己的 toolMsg」那条结论
是三轮实测才定下来的,并行会直接破坏它。多模态插件在 3 处调
SetToolBlocks(multimodal/plugin.go:136,246,320),是真实使用面。

改动:
· 字段 toolPendingBlocks: []interface{} → map[string][]interface{}
· 新增 SetToolBlocksFor / ConsumeToolBlocksFor / ClearToolBlocks(按 call_id)
· 保留 SetToolBlocks / ConsumeToolBlocks 作兼容入口(走 callID=""),
  存量调用方与串行单工具场景不受影响;其注释写明并行下该路径不可靠

判据(toolblocks_test.go,4 条):
· 两个 call 各自注入、**交叉顺序**并发取回,各归其主
· 取走即消费(二次取回为空)
· 未知 callID 返回空且**不影响他人**的块
· 32 路并发零串味

变异说明:本阶段是从「单槽」直接改为 per-call,旧实现在这四条判据下
(per-call API 不存在)无法编译通过,等价于判据先失败;per-call 版
再经 `go test -race` 验证无数据竞争。回归:internal/agent/io 全绿。
2026-09-27 11:06:41 +08:00