fix(mobile): 幂等化 schedule 镜像失效,修复 Android 冷启动无限更新崩溃 - #4252
Conversation
Presence 离线标记会调用 invalidateDeviceMirror,它非幂等:重复标记 (presence 闪断、首页与 device-link 两层各标一次)每次都 bump generation 并 emit,叠加详情页 effect 以持久失效标记和 sessions 引用为依赖,离线 转换时把挂载中的屏幕反复拉进 React 更新链,超过嵌套上限后以 Maximum update depth exceeded 致命退出(2026-09-10 Android 冷启动 约 5.5s,精确踩中 5s 离线宽限期)。 - invalidateDeviceMirror 幂等:marker 已存在且没有新事件快照时不再 重复 emit;快照被新事件重新写入后照常清除并广播一次,闪断周期里 合法的二次失效不受影响。 - 详情页 effect 对同一 generation 只消费一次;sessions 引用变化不再 重复入链,会话列表晚到的场景由 syncSessions 的权威索引重载兜底。 - 改写 remoteScheduleEvents 测试编码新语义,并按屏幕 effect 实测 语义补充 consume-once 回归测试。 纯 JS 改动,不触碰 runtime fingerprint 输入,改动前后 ci-fingerprint 哈希一致(iOS 8545caf7 / Android 70a05119),经 OTA 热更即可到达存量装机,不触发冷更。 Signed-off-by: guyong <guyong@xd.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 872d7ef709
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
| Filename | Overview |
|---|---|
| apps/mobile/src/scheduler/remoteScheduleEvents.ts | 为设备 schedule 镜像失效增加幂等 guard,并保留新事件后的重新上膛语义。 |
| apps/mobile/app/devices/[deviceId].tsx | 新增按 generation 的 consume-once 逻辑,但可能遗漏消费时未包含在过滤后 sessions 中的 schedule 条目。 |
| apps/mobile/src/tests/remoteScheduleEvents.test.ts | 覆盖重复失效静默及新事件后再次失效广播的 store 语义。 |
| apps/mobile/src/tests/deviceScheduleVisibility.test.ts | 通过提取 effect 验证 generation 去重,但未覆盖过滤范围变化与离线同步失败的组合路径。 |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
E[Schedule 事件到达] --> S[写入 snapshot / 重新上膛]
O[设备离线] --> I[invalidateDeviceMirror]
S --> I
I --> G{已有 marker 且无新 snapshot?}
G -->|是| N[静默返回]
G -->|否| B[递增 generation 并广播]
B --> C{详情页已消费该 generation?}
C -->|否| L[按当前 sessions 清除 running 状态]
C -->|是| K[跳过处理]
K --> R[后来可见的 session 可能保留旧 running 状态]
R --> A[成功权威同步后恢复]
Prompt To Fix All With AI
### Issue 1
apps/mobile/app/devices/[deviceId].tsx:277-278
**遗漏后到会话失效**
当本次 generation 首次消费时,`sessions` 可能因项目范围过滤而不包含 `scheduleIndex` 中已有的 running session。该 session 随后因范围变化重新可见时,这个 guard 会直接跳过再次失效;如果设备仍离线且 `sessions:list` 失败,现有错误路径也不会清空旧的 schedule index,用户会在整个离线期间看到错误的“自动化执行中”状态。这里需要让同一 generation 覆盖后来进入当前列表的 session,或按 generation 记录已经处理的 session。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix(mobile): 幂等化 schedule 镜像失效,修复 Androi..." | Re-trigger Greptile
|
本 PR 的维护者确认门因测试中的 dynamic-eval( 请维护者直接在 PR 上 Approve;需要作者改测试则 Request Changes。讨论 issue:#4256 |
- invalidateDeviceMirror 的 generation 改为跨 marker 清除单调: clearDeviceMirrorInvalidation 只清标记不清计数器,离线 → 恢复 → 再离线时消费方不会被已消费的回绕代次挡住(codex P2)。 - 详情页消费记录标记清除时同步丢弃;同一 generation 内后进入可见 列表的会话按增量补清 running,不再整轮跳过(greptile P1)。 - 测试补跨清除单调、增量清理与消费记录重置断言。 ci-fingerprint 前后一致(iOS 8545caf7 / Android 70a05119)。 Signed-off-by: guyong <guyong@xd.com>
07174e3 to
1dd303d
Compare
这次改了什么
摘要
配套硬化 PR(崩溃主修复在 #4259 批量合并离线镜像通知,两 PR 相互独立、不互相依赖)。
背景:2026-09-10 Android 端 JS 层
Maximum update depth exceeded致命崩溃,隔离复现定位为剂量依赖机制——presence 整批离线时每台设备各触发一轮 store 通知,设备数超过 React 嵌套更新上限(约 50)即致命退出(40 台正常,80 台第 53 次通知崩,同批处理仅 3 次渲染正常)。本 PR 修掉排查中确认的三个真实缺陷,降低崩溃波里的重复通知与消费错乱(非主修复):
invalidateDeviceMirror幂等:marker 已存在且没有新事件快照时不再重复 emit(presence 闪断、首页与 device-link 两层对同一设备重复标记时去重);快照被新事件重新写入后照常清除并广播一次。sessions引用变化不再重复入链;同一代次内后进入可见列表的会话按增量补清 running;标记清除时同步丢弃消费记录。变更类型
fix缺陷修复范围
UI 变化
怎么验证的
自动验证
手工验证
不涉及(无实机回归条件)。
未执行的验证
Android 实机验证未在本地执行;语义已由 store 层与屏幕 effect 回归测试覆盖。
风险
风险分类
影响与回滚
apps/mobile的 schedule 镜像失效广播与详情页 effect 消费语义;不改变 runtime fingerprint,不触发冷更。提交前检查
git commit -s,见 DCO)