91 lines
7.0 KiB
Markdown
91 lines
7.0 KiB
Markdown
---
|
|
id: T-606
|
|
title: 收紧图标缓存并发与内存边界
|
|
phase: 2
|
|
deps: [T-204, T-605]
|
|
status: TODO
|
|
created: 2026-07-17
|
|
issue: null
|
|
context_ref: null
|
|
claim_branch: null
|
|
work_branch: null
|
|
write_paths:
|
|
- 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 快照和文件拆分继续按审核顺序串行拆分。
|