Files
soft_quay/docs/tasks/T-606.md

8.8 KiB

id, title, phase, deps, status, created, issue, context_ref, claim_branch, work_branch, write_paths
id title phase deps status created issue context_ref claim_branch work_branch write_paths
T-606 收紧图标缓存并发与内存边界 2
T-204
T-605
DONE 2026-07-17 null 8873a5261d null agent/codex/T-606
docs/tasks/T-606.md
core/catalog/
app-modern/ui/gio/
app-win7/ui/gio/
docs/api.md
docs/routes.md
docs/04-architecture.md
docs/05-coding-rules.md
docs/review/phase2-review.md
docs/00-ai-start-here.md
docs/06-tasks.md
docs/current-state.md

问题 / 背景

Phase 2 交叉审核确认,T-204 的 IconCache.Load 从取得全局 mutex 到返回全程持锁,锁内包含磁盘读取、图片校验、远端 Fetch 和磁盘原子写。一个慢图标会阻塞所有其他 key,包括本应立即返回的内存命中;简单地把 Fetch 移出锁又会使同一 key 重复下载并竞争写盘。

现有 IconFetcher 直接返回完整 []byte,IconCache 只能在 Fetch 完成后检查 2 MiB 上限。该检查能阻止超大对象进入缓存,却不能阻止未来网络 Fetcher 先下载并分配任意大响应。与此同时,IconCache.memory 没有容量/LRU 上限,modern/win7 的 shell.icons 也不会在 Catalog 移除 app 后剪枝,长期运行时内存只增不减。

当前 IconCache.Load 尚无生产调用者,T-204 也明确把真实图标分发映射与网络装配留给后续,所以这些不是已经复现的滚动验收失败。它们仍是正式图标后台加载接入前必须关闭的并发与资源边界。

方案

  1. 把 IconFetcher 调整为可有界读取的流式响应合约,由 IconCache 掌握读取边界与关闭责任:
    • 响应提供 io.ReadCloser body 和可选的声明长度;未知长度使用明确哨兵值。
    • 声明长度超过 maxBytes 时在读取 body 前拒绝。
    • 对所有响应使用 io.LimitReader(maxBytes+1) 或等价逻辑;实际读到上限加一字节时返回 ErrIconTooLarge。
    • 成功、校验失败、读取失败、context 取消等所有路径都关闭 body,并保留关闭错误的既定错误优先级。
    • 不在本任务猜测 Catalog 内容哈希到 URL/DPI 变体的映射,也不实现具体发布端协议;后续 HTTP 适配器必须使用该流式合约。
  2. 为 IconCache 建立 Go 1.20 兼容的按 cache key in-flight 去重:
    • 全局 mutex 只保护 memory/LRU/in-flight 元数据,不横跨磁盘、网络、图片解码或磁盘写入。
    • 同一 digest+DPI 只允许一个 leader 执行 disk→fetch→validate→store,其余调用者等待并复用结果。
    • 不同 key 可以并行,慢 key 不阻塞另一 key 的 memory hit。
    • follower 的 context 取消只停止自身等待,不得取消 leader;leader 失败/取消后发布同一结果、移除 flight,后续调用可重试。
    • 每个调用者取得独立的结果字节副本,不能修改 cache 或其他等待者观察到的内容。
  3. 将内存层改为按 key 的 LRU,同时限制总字节与条目数:
    • 默认上限冻结为 32 MiB、256 个 key;命中提升 recency,插入/替换时更新准确字节计数并持续淘汰最旧项直到两个上限同时满足。
    • 同一 digest 的不同 DPI 仍是不同 key;磁盘缓存与现有内容校验/原子写语义不变。
    • LRU 操作与 in-flight 完成发布在同一同步纪律下,不得产生负计数、重复 list 节点或返回已被复用的内部切片。
  4. modern/win7 的 AppShell.SetItems 按新 Catalog app ID 集合剪枝 shell.icons,保留仍存在 ID 的 paint.ImageOp;与现有 rows/categories 剪枝语义对齐。
  5. 扩展并发、流边界、LRU 与双 Gio 测试,使用 channel/barrier 等确定性同步,不以 time.Sleep 猜测并发时序。
  6. 同步图标缓存协议、架构与编码规则,明确“内存→磁盘→流式 Fetcher”的边界、内存容量、按 key 去重以及真实 UI 接入仍须走 application event/UI goroutine。

验收要点

  • 一个阻塞的远端 key 不会阻塞另一 key 的内存命中;两个不同远端 key 能实际重叠执行。
  • 同一 key 的并发 miss 只执行一次 Fetch 和一次成功存储,所有等待者获得内容相同但 backing array 独立的结果。
  • follower context 取消能及时返回且不影响 leader;leader 失败或取消后 in-flight 被清理,下一次调用可以重新 Fetch。
  • 声明 Content-Length 超过 2 MiB 默认上限时不读取 body;未知/伪造较小长度但实际 body 超限时最多读取 maxBytes+1;所有路径关闭 body。
  • 哈希不符、非法图片、磁盘损坏在线修复、离线读取已验证磁盘缓存、DPI 分键和非致命磁盘写 warning 的既有语义保持通过。
  • 默认 memory LRU 同时受 32 MiB 与 256 key 限制;命中提升顺序,替换正确更新字节数,淘汰后可从磁盘/远端重新装入。
  • modern/win7 SetItems 删除 app 后对应 shell.icons 项消失,保留 app 的 ImageOp 不变;rows/categories 既有稳定性测试继续通过。
  • 并发测试使用确定性同步并可重复运行;若当前工具链支持 race detector,执行 go test -race 并记录结果,不把 race 支持作为 Win7 交叉构建的前提。
  • Go 1.20.14 + GOWORK=off 下 go vet ./...、go test -count=1 ./... 通过。
  • modern 根 workspace 与 win7 独立 workspace 的 ui/gio 测试和 Windows amd64 构建通过;./scripts/verify_phase0.ps1 全绿。
  • python scripts/validate_agent_context.py、python scripts/validate_harness_governance.py 与提交前差异检查通过。

边界(不改什么)

  • 不决定 sha256: 图标引用到下载 URL、CDN 路径或 DPI 变体的发布端映射,不增加未经协议冻结的 Catalog 字段。
  • 不把 IconCache 正式装配进应用启动或列表滚动链路;后台事件→UI goroutine→ApplyIcon/Invalidate 的接线按审核顺序另立后续任务。
  • 不补 modern/win7 完整适配器交互契约测试,不修改 VisibleItems 快照 API,不拆分 shell.go;这些按审核后续顺序分别处理。
  • 不对磁盘图标缓存实现 LRU/容量清理,不自动删除或 quarantine symlink/reparse point;ErrIconCacheUnsafe 继续 fail closed。
  • 不引入 x/sync/singleflight 或其他第三方缓存库,不升级 Go/Gio,core 与 win7 继续保持 Go 1.20 兼容。
  • 不处理 ZIP 中央目录预扫描、断电耐久、Catalog 签名向量或 T-302 安装整合。
  • 不修改、提交或删除用户的 soft_quay.code-workspace。

协作约束

  • 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
  • 本任务只允许修改 frontmatter 中的 write_paths;若流式 Fetcher 需要新增公共协议字段或真实 URL 规则,必须停止并记录,不得在 T-606 内猜测协议。
  • T-606 完成、完整验证并提交前,不落成或领取 Phase 2 审核顺序中的后续任务。

执行记录

  • 2026-07-17:根据 docs/review/phase2-review.md 交叉复核定稿的第一优先级整改落成任务;现有全局最大任务为 T-605,因此取 T-606。
  • 2026-07-17:任务合并收口按 key in-flight、流式有界读取、memory LRU 与双 shell 图标剪枝;UI 线程投递、适配器契约、VisibleItems 快照和文件拆分继续按审核顺序串行拆分。
  • 2026-07-17:在 agent/codex/T-606 分支领取任务,基线为 8873a5261db76b30db30521197e9ec898ca86077;保持单 Agent 串行执行。
  • 2026-07-17:IconFetcher 改为返回 context-bound io.ReadCloser 与可选声明长度;IconCache 在分配完整响应前拒绝超大声明并只读取 maxBytes+1,成功、超限、取消和校验失败路径统一关闭 body。未增加 URL/CDN 字段或具体 HTTP 映射。
  • 2026-07-17:IconCache 全局 mutex 收缩为只保护 memory/LRU/in-flight 元数据;同 digest+DPI 使用单 leader,不同 key 的磁盘/网络工作可并行,follower 可独立取消,leader 失败后 flight 清理并允许重试。每个调用者与 LRU 都持有独立字节副本。
  • 2026-07-17:新增默认 32 MiB/256-key 的双上限 LRU,覆盖命中提升、替换计数、条目/字节淘汰、禁用边界与淘汰后 verified disk 重载;modern/win7 SetItems 同步剪枝已移除 app 的 ImageOp 并保留现存 app 图标。
  • 2026-07-17:定向验证通过:Go 1.20.14 go vet ./catalog、go test -count=20 ./catalog;modern/win7 go test -count=5 ./ui/gio。并发用例使用 channel/barrier 与 2 秒仅防挂死超时,覆盖跨 key 并行、memory hit 不受慢 key 阻塞、同 key 单 Fetch、follower/leader 取消、flight 重试、body 最大读取量和独立 backing array。
  • 2026-07-17:尝试 Go 1.20.14 go test -race -count=1 ./catalog;当前 Windows 环境缺少 GCC(cgo: C compiler "gcc" not found),race detector 不可用。按任务边界记录限制,未把它伪报为通过;确定性并发测试连续 20 次通过。
  • 2026-07-17:完整 ./scripts/verify_phase0.ps1 通过,包含治理/上下文/边界/版本校验、Go 1.20.14 core vet/test、modern Go 1.25 与 win7 Go 1.20.14 的 UI/平台测试及 Windows amd64 构建。