Skip to content

fix(mobile): 幂等化 schedule 镜像失效,修复 Android 冷启动无限更新崩溃 - #4252

Draft
guyong-zapo wants to merge 2 commits into
mainfrom
fix/mobile-schedule-mirror-update-depth
Draft

fix(mobile): 幂等化 schedule 镜像失效,修复 Android 冷启动无限更新崩溃#4252
guyong-zapo wants to merge 2 commits into
mainfrom
fix/mobile-schedule-mirror-update-depth

Conversation

@guyong-zapo

@guyong-zapo guyong-zapo commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

这次改了什么

摘要

配套硬化 PR(崩溃主修复在 #4259 批量合并离线镜像通知,两 PR 相互独立、不互相依赖)。

背景:2026-09-10 Android 端 JS 层 Maximum update depth exceeded 致命崩溃,隔离复现定位为剂量依赖机制——presence 整批离线时每台设备各触发一轮 store 通知,设备数超过 React 嵌套更新上限(约 50)即致命退出(40 台正常,80 台第 53 次通知崩,同批处理仅 3 次渲染正常)。

本 PR 修掉排查中确认的三个真实缺陷,降低崩溃波里的重复通知与消费错乱(非主修复):

  1. invalidateDeviceMirror 幂等:marker 已存在且没有新事件快照时不再重复 emit(presence 闪断、首页与 device-link 两层对同一设备重复标记时去重);快照被新事件重新写入后照常清除并广播一次。
  2. 详情页 effect 对同一失效代次只消费一次;sessions 引用变化不再重复入链;同一代次内后进入可见列表的会话按增量补清 running;标记清除时同步丢弃消费记录。
  3. 失效代次跨 marker 清除单调(离线 → 恢复 → 再离线不会被回绕代次挡住)。

变更类型

  • fix 缺陷修复

范围

UI 变化

  • 引用的设计规范:不涉及。纯状态机/更新链逻辑修复,无视觉、交互或文案变化(命中 UI 路径的文件为设备详情页 screens,改动仅限 effect 触发语义与 store 幂等)

怎么验证的

自动验证

pnpm test:unit:related
结果:apps/mobile 全部通过;desktop 与 maker-core 存在与本改动无关的既有本机失败
(已隔离验证,以 CI 为准)。

pnpm --filter mobile run typecheck
结果:通过。

node apps/mobile/scripts/ci-fingerprint.mjs compute(改动前后比对)
结果:iOS 8545caf7c05b591dc81a460f7ed6cd12f3e6f339 / Android 70a051193695c4af3775baa93c0d6db5e2c8bd82
前后完全一致 → 不触发冷更。

手工验证

不涉及(无实机回归条件)。

未执行的验证

Android 实机验证未在本地执行;语义已由 store 层与屏幕 effect 回归测试覆盖。

风险

风险分类

  • 无已知风险
  • 原生层 / fingerprint / OTA(仅指 OTA 下发,不触发冷更)

影响与回滚

  • 影响范围:纯 JS 改动,只影响 apps/mobile 的 schedule 镜像失效广播与详情页 effect 消费语义;不改变 runtime fingerprint,不触发冷更。
  • 回滚 / 降级方式:revert 本 PR 即可。

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带 DCO 签名(git commit -s,见 DCO)
  • UI 改动已在「UI 变化」注明(不涉及,附理由)
  • 未提交凭证、令牌或授权文件
  • 已确认测试结果或说明未执行原因

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>
@guyong-zapo
guyong-zapo requested a review from a team as a code owner September 10, 2026 12:59

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/mobile/app/devices/[deviceId].tsx Outdated
@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown

Greptile Summary

本 PR 将移动端 schedule 镜像失效广播改为幂等,并让设备详情页按 generation 去重消费,以避免离线转换期间重复更新导致 React 更新深度崩溃。

  • remoteScheduleEventStore 在失效 marker 尚存且没有新事件时不再重复广播。
  • 新事件写入 snapshot 后会重新上膛,后续真实失效仍会生成新 generation。
  • 设备详情页加入 generation 消费记录,但当前实现可能遗漏消费时不在过滤后 session 列表中的 schedule 条目。
  • 两组回归测试覆盖 store 幂等和同一 generation 不重复调用 state updater 的语义。

Confidence Score: 4/5

当前不建议直接合并,需要先处理 consume-once 逻辑遗漏后来进入当前 session 范围的 schedule 条目这一问题。

store 层幂等逻辑能够正确重新上膛,但详情页在记录整个 generation 已消费后,只失效首次执行时可见的 session;范围变化叠加离线同步失败会留下错误的 running 状态。

Files Needing Attention: apps/mobile/app/devices/[deviceId].tsx

Important Files Changed

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[成功权威同步后恢复]
Loading
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

Comment thread apps/mobile/app/devices/[deviceId].tsx Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

本 PR 的维护者确认门因测试中的 dynamic-eval(new Function 抽出详情页 effect)触发 security 项,不能按 bugfix 豁免。product 路径虽命中,但完整 diff 是内部崩溃修复、用户界面零像素差异,不单独作为产品确认项。

请维护者直接在 PR 上 Approve;需要作者改测试则 Request Changes。讨论 issue:#4256

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:security 改动碰到安全边界(review-pr 自动维护,仅展示) labels Sep 10, 2026
- invalidateDeviceMirror 的 generation 改为跨 marker 清除单调:
  clearDeviceMirrorInvalidation 只清标记不清计数器,离线 → 恢复 →
  再离线时消费方不会被已消费的回绕代次挡住(codex P2)。
- 详情页消费记录标记清除时同步丢弃;同一 generation 内后进入可见
  列表的会话按增量补清 running,不再整轮跳过(greptile P1)。
- 测试补跨清除单调、增量清理与消费记录重置断言。

ci-fingerprint 前后一致(iOS 8545caf7 / Android 70a05119)。

Signed-off-by: guyong <guyong@xd.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-discussion 等待维护者讨论(review-pr) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:security 改动碰到安全边界(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants