对账整段设界,并把提交前置预算变成真正的取消
Project CI / Repository checks (pull_request) Successful in 1m5s
Project CI / Frontend tests (pull_request) Successful in 3m9s
Project CI / Backend tests (pull_request) Successful in 4m4s
Project CI / Native shell tests (pull_request) Successful in 11m20s

对账用 Promise.all 同时等项目快照与 refreshAssetLibrary,而 loadEditorAssetLibrary
没有 timeoutMs。catch 也接不住永不 settle:await 不返回则 finally 永远不执行,
占位归属登记与源图层锁都释放不掉,而到期清理又豁免已登记的占位,页面永久停在
generating。这个洞是上一条留下的——给 loadEditorProject 加界时只覆盖了同一个
Promise.all 里两个 await 中的一个。

改为给整段对账设 75 秒上界,往里加新的 await 自动受约束。超时解析为 null 而不
拒绝:这段在 catch 内,抛出会穿出 async 函数变成未处理 rejection;null 则落进
既有的「快照读取失败」分支。

前置预算原先只是 Promise.race,超时后上传继续跑到 confirm 并注册对象。把同一个
AbortSignal 贯穿凭证、直传与 confirm 三步,由预算到期时 abort——只停其中一步会
留下半成品。未做重试等待与 flush 的贯穿:收益是少产生不可见的存储孤儿,而 flush
已有 60 秒上界。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 12:06:31 +00:00
parent 8d1bee9b1a
commit 89914b2255
6 changed files with 129 additions and 9 deletions
@@ -6171,3 +6171,15 @@
- 真正的问题是验证方式而非测试:改的是 `src/services/image-editor/editorProjectClient.ts`,验证却只跑了 `vitest src/components/image-editor`,改动面与验证面完全对不上。这个盲区在本次会话中期分析另一份 CI 日志时已由我自己指出过,却没有改掉习惯,于是同一个盲区再次漏出——而且这次不是难复现的跨文件竞态,是本地一跑就红的确定性失败。
- 约定:这条链路横跨 `src/components/image-editor/``src/services/image-editor/`,往后验证至少同时覆盖两处。不跑全量套件——本机有九条稳定的环境失败(符号链接、`0600` 权限模式、缺客户端 AppData 配置),噪音大于收益。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
## 2026-08-03 对账整段设界,并把提交前置预算变成真正的取消
- 缺陷一(对账可被素材库读取永久挂住):对账用 `Promise.all` 同时等项目快照与 `refreshAssetLibrary`,而 `loadEditorAssetLibrary` 至今没有 `timeoutMs``composeAbortSignal` 缺失时不设默认值)。`.catch()` 只接住拒绝、接不住永不 settle;catch 体内的 await 不返回,`finally` 就永远不执行——占位归属登记与源图层锁都释放不掉,而到期清理又豁免已登记的占位,页面永久停在 `generating`,同一源图也无法再次操作,刷新前无解。
- 这个洞是上一条修复留下的:给 `loadEditorProject` 加界时写的注释已经把机制说对了(「挂住会让 catch 迟迟不结束」),却只给同一个 `Promise.all` 里两个 await 中的一个加了界。逐个接口补超时这条路已经漏过一次。
- 决策一:给**整段对账**设 75 秒上界,而不是继续逐个接口补。往对账里加任何新的 await 都自动受约束。超时必须**解析为 null 而不是拒绝**——这段代码本身位于 catch 内,抛出会穿出整个 async 函数,而调用方是 `void snapSelectedLayerToPerfectPixels(...)`,结果是未处理的 rejection;解析为 null 则落进既有的「权威项目快照读取失败」分支,语义正好一致。
- 缺陷二(预算只停止等待、不取消):`withPerfectPixelPrePostBudget` 原先只是 `Promise.race`,超时后底层继续跑。直传 `fetch` 没有 signal,被放弃的上传会一路走到 confirm 并注册对象,用户重试再产生一份。
- 决策二:把同一个 `AbortSignal` 贯穿凭证请求、直传 POST 与 confirm 三步,由前置预算到期时 `abort`。只中止直传会留下未 confirm 的 OSS 对象,只中止 confirm 又会让实体已写入却无记录——要停就整条链一起停。`requestJson``init.signal` 取信号并与自身超时合成,所以凭证与 confirm 只需在 init 里传入。
- 未采纳评审建议的全量贯穿(再覆盖重试等待与 flush/save):那要再动两个模块,而收益只是少产生一些用户不可见的存储孤儿;`flushProjectPersistence` 现已有 60 秒上界,最多多挂 60 秒后自行结束。改动面从四个模块降到两个,绝大部分收益保留。
- 后果分级要说清:缺陷一是永久性的 UI 卡死,缺陷二只是存储层孤儿对象(confirm 注册的是 asset object,不是素材库条目,用户基本不可见)。两者同为 P2 但不同量级。
- 验证:新增用例让项目快照读取永不 settle,断言 120 秒后完美像素状态回到「空闲」——即 `finally` 确实执行。去掉对账 deadline 后该用例变红。`vitest src/components/image-editor src/services/image-editor` 974 通过,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`