AGC 项目快照换号后全量重传 + IPC 定制协议失败,造成客户端 15s 超时与「卡住」 #504
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
关联 #490(状态被钉在「检查失败」的下游症状);本 issue 补上 #490 里「停顿发生在哪一层(远端网络 / IPC 回传 / WebView 主线程)尚未定位」的那一层。
现象
/api/runtime/frontend-config、/api/llm/models、/api/profile/recharge-center报「请求超时(15000 ms)」。POST /api/agc/project-snapshots/files刷屏(200、latency_ms58~61ms、每 60ms 一条)。复现(2026-09-24 10:54~10:58,本机 dev 客户端 + 本地 api-server:8082)
操作:CDP attach 客户端 WebView2 → 注入探针 → 点「春卷冲刺」。
diagnostics/application.log10:57:37 project_snapshot.sync.completed projectId=gameagent-6e43b49b… trigger=project-open status=synced revision=1 uploaded=2311 skippedRemote=2306logs/api-server/api-server-20260924-104207.logPOST /api/agc/project-snapshots/files(按分钟 278 / 1259 / 774),avg 45.8ms,全部 200;整份日志 0 个 4xx/5xxfrontend-config10 次、recharge-center7 次、llm/models2 次,全部 200 且 ≤61ms一次「打开项目」= 2311 个文件全传一遍,耗时约 3 分钟;其中 2306 个服务端 HEAD 命中,纯白跑。
根因 A:换号后本地快照索引判空 → 全量重传
%APPDATA%\world.genarrative.ai-game-creator\project-snapshots\<projectId>\index.json按项目存放、只记一个userId;project_snapshot/mod.rs里previous.user_id != session.user_id就用空索引(project_snapshot/index.rs),于是没有基线 → 全量上传。userId=user_5916a7…、revision=2;点之后写成userId=user_a404cb29…、revision=1→ 当前登录账号与索引记录的账号不同。skippedRemote=2306,远端已有,纯属白跑)。200 + skipped,所以这种白跑完全静默,只能靠服务端日志量暴露。根因 B:15s 超时出在客户端 IPC 回传,不是后端
IPC custom protocol failed, Tauri will now use the postMessage interface instead TypeError: Failed to fetch刷屏:当前application.log58 行里 51 行是它(10:56:26–10:57:40 ≈ 0.7 次/秒);空闲时段(09-23 16:14 → 10:56,847 行)只有 59 次 → 失败率放大近三个数量级。每次失败写一行 ~330 字节,日志因此约 10 分钟轮转一次(application.previous.log恰好 262145 字节)。@tauri-apps/plugin-http(src/services/clientHttp.ts:342),请求与响应都必须穿过这条 IPC;通道在重传压力下退化后响应回不到 renderer,JS 侧 15s 计时器先到,于是表现为「请求超时」,而服务端早已 200 返回。待补证据
__TAURI_INTERNALS__.invoke与__TAURI__.core.invoke都是 non-writable + non-configurable,页面内无法 patch,需要在Page.addScriptToEvaluateOnNewDocument里于页面加载前注入并重启一次客户端。Network.loadingFailed的blockedReason/corsErrorStatus。打算怎么改
project-snapshots/<projectId>/<userId>/index.json),或userId变化时不直接判空、先拉远端 manifest 比对;需要客户端可用的只读接口(新增GET /api/agc/project-snapshots/manifest)。http://ipc.localhost在负载下失败的原因;把「每次调用一条 warn」降为「每会话一条」,避免日志雪崩与额外写盘。inspectRecentWorkspaceWithRetry一类重试叠加。验证
project-snapshots/files请求数应从 2311 降到仅差异文件数,日志不再刷屏。application.log不再出现IPC custom protocol failed。cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project_snapshot;门禁npm run check:encoding、git diff --check。按 #505 的结论收口:不做客户端落地,改由「数据与 IO 往后端挪」的架构迁移覆盖,本 issue 以 Won't Fix 关闭。
现状(master,真机可复现)
打开项目会整项目重传:春卷冲刺 2311 个文件 / 约 3 分钟 / 2311 次
POST /api/agc/project-snapshots/files,其中 2306 次服务端 HEAD 命中(200 + skipped)纯白跑;同窗口内/api/runtime/frontend-config、/api/llm/models、/api/profile/recharge-center在服务端 200 且 ≤61ms 的情况下被客户端报「请求超时(15000 ms)」,并触发 #490 的「检查失败」钉死。根因是本地索引整份只保存一个账号的基线,换号被等价成「本机没有基线」。关闭原因
本 issue 修的「哪些文件需要上传」正是可以上移到后端的那一层(判定归属)。客户端再落一套基线分桶会与迁移后的实现形成两套判定,因此 #505 关闭,实现只保留在分支
fix/api-timeout(commit3be8e40bc,定向测试 26 passed / 0 failed),迁移完成后若问题仍在可直接复用该分支。分层说明:本 issue 的客户端改动只碰本地持久化格式(
index.json)与一个 native-only 诊断视图的字段,没有触碰客户端与服务端的路由、DTO、对象键和清单结构,即不涉及协议层;差异判定上移后新增的 manifest / diff 交互才是协议层变更。交给迁移侧(不随实现位置改变的三条约束)
(userId, projectId)两元组归属——远端对象键第一段就是userId,换号后旧基线对新账号无效,任何新设计都不能跨账号共用或互相覆盖。扫描 + 读字节 + 发字节必须留在客户端,「差异判定」才是可上移的那一层。复核与重新打开条件
迁移上线后按原复现步骤核对三条:① api-server 侧
project-snapshots/files请求数等于真实差异文件数(不再是文件数);② 换号后首次打开该项目不再整项目重传;③ 该窗口内三个平台接口不再 15s 超时、客户端diagnostics/application.log不再出现IPC custom protocol failed。任一条不成立就直接恢复跟踪(排障口径:客户端project_snapshot.sync.*与 api-server 各 route 的http.response按同一时间窗两侧对齐)。建议迁移侧有独立的 issue / 里程碑文档承载「数据与 IO 往后端挪」,并在其中引用本 issue 收口——这条链路目前没有对应的规范文档。
@kdletters