IOS-INFO-039:Runtime 启动恢复、stale events 重载与异常 session 恢复 #127

Closed
opened 2026-06-10 22:15:05 +08:00 by wangdl · 1 comment
Owner

目标

处理 App crash、系统 kill、内存压力终止、export 后未 ack 等异常场景,保证 Rust Runtime 与 iOS 本地队列的状态可恢复。

背景

Rust document runtime 已提供 reload_stale_events_v2,用于启动时将 Exported 但未 ack 的事件重置为 Pending,避免 iOS export 后 crash 导致事件长期停留在 Exported 状态。

iOS 必须在启动恢复流程中调用该能力。

需要处理的场景

  • App 启动
  • App crash 后重新启动
  • App 被系统 kill 后重新启动
  • 阅读页中 crash
  • export 成功但写本地队列前 crash
  • export 成功且写本地队列成功但 ack 前 crash
  • session 未 close
  • 长时间后台后被系统终止

恢复流程建议

App 启动
  ↓
初始化 ReadingRuntimeAdapter
  ↓
调用 reload_stale_events_v2
  ↓
查询 Runtime buffer state
  ↓
检查本地 ReadingEventUploadQueue
  ↓
恢复 pending upload
  ↓
标记未正常关闭 session 为 interrupted,若 Rust 支持
  ↓
允许后续正常 open 新 session

规则

  1. reload_stale_events_v2 必须在 App 启动或 Runtime 初始化后尽早调用。
  2. reload_stale_events_v2 调用失败不能导致 App 启动失败,但必须记录诊断日志。
  3. 已写入本地队列但未 ack 的事件,通过 eventId 去重避免重复上传。
  4. 未写入本地队列且未 ack 的 exported event,应由 reload_stale_events_v2 恢复为 pending。
  5. 未 close 的 session 应在启动恢复时标记 interrupted 或通过后续 close fallback 处理。
  6. 新 session 打开前不能复用已异常中断的旧 session。
  7. 恢复过程不得清空本地上传队列。

验收标准

  1. App 启动时会调用 reload_stale_events_v2。
  2. reload_stale_events_v2 失败时记录日志但不崩溃。
  3. export 后未 ack 的事件可重新进入 pending。
  4. 本地队列通过 eventId 去重,不重复写入。
  5. 未 close session 可被标记 interrupted 或安全废弃。
  6. 新 session 不复用异常 session。
  7. 有恢复流程测试或手动验收脚本。
  8. 文档说明恢复策略。
## 目标 处理 App crash、系统 kill、内存压力终止、export 后未 ack 等异常场景,保证 Rust Runtime 与 iOS 本地队列的状态可恢复。 ## 背景 Rust document runtime 已提供 reload_stale_events_v2,用于启动时将 Exported 但未 ack 的事件重置为 Pending,避免 iOS export 后 crash 导致事件长期停留在 Exported 状态。 iOS 必须在启动恢复流程中调用该能力。 ## 需要处理的场景 - App 启动 - App crash 后重新启动 - App 被系统 kill 后重新启动 - 阅读页中 crash - export 成功但写本地队列前 crash - export 成功且写本地队列成功但 ack 前 crash - session 未 close - 长时间后台后被系统终止 ## 恢复流程建议 ``` App 启动 ↓ 初始化 ReadingRuntimeAdapter ↓ 调用 reload_stale_events_v2 ↓ 查询 Runtime buffer state ↓ 检查本地 ReadingEventUploadQueue ↓ 恢复 pending upload ↓ 标记未正常关闭 session 为 interrupted,若 Rust 支持 ↓ 允许后续正常 open 新 session ``` ## 规则 1. reload_stale_events_v2 必须在 App 启动或 Runtime 初始化后尽早调用。 2. reload_stale_events_v2 调用失败不能导致 App 启动失败,但必须记录诊断日志。 3. 已写入本地队列但未 ack 的事件,通过 eventId 去重避免重复上传。 4. 未写入本地队列且未 ack 的 exported event,应由 reload_stale_events_v2 恢复为 pending。 5. 未 close 的 session 应在启动恢复时标记 interrupted 或通过后续 close fallback 处理。 6. 新 session 打开前不能复用已异常中断的旧 session。 7. 恢复过程不得清空本地上传队列。 ## 验收标准 1. App 启动时会调用 reload_stale_events_v2。 2. reload_stale_events_v2 失败时记录日志但不崩溃。 3. export 后未 ack 的事件可重新进入 pending。 4. 本地队列通过 eventId 去重,不重复写入。 5. 未 close session 可被标记 interrupted 或安全废弃。 6. 新 session 不复用异常 session。 7. 有恢复流程测试或手动验收脚本。 8. 文档说明恢复策略。
wangdl added this to the M-IOS-INFO:学习信息采集、上传、继续学习与基础分析闭环 milestone 2026-06-10 22:15:05 +08:00
Author
Owner

开发完成评论

完成内容

  • 新建 Core/Services/RuntimeRecoveryService.swift(57 行):
    • run(adapter:) — 完整恢复流程入口
    • Step 1: adapter.reloadStaleEvents() → 重置 exported-but-unacked 事件为 pending
    • Step 2: adapter.cleanupStaleSessions(nowMs, maxAgeMs: 24h) → 清理过期 session
    • Step 3: 检查本地 upload queue pendingCount + 触发 ReadingEventUploadPipeline.reloadOnLaunch()
    • 错误不传播:reloadStaleEvents 返回 UInt32(不抛),cleanup 也不抛
    • 诊断日志全部用 print(),不依赖 OSLog(兼容性)
  • 接入 AIStudyAppApp.swiftscenePhase == .active 时在 SessionManager.resume() 之前调用 RuntimeRecoveryService.run()
  • 新建 AIStudyAppTests/RuntimeRecoveryServiceTests.swift(4 个测试):
    • Noop adapter 恢复不崩溃
    • reloadStaleEvents 调用返回 0
    • cleanupStaleSessions 返回 0
    • 多种 maxAge 不崩溃

修改文件

  • Core/Services/RuntimeRecoveryService.swift:新建
  • AIStudyAppApp.swift:79-80:集成恢复调用
  • AIStudyAppTests/RuntimeRecoveryServiceTests.swift:新建

验收标准逐项

  1. App 启动时会调用 reload_stale_events_v2 — RuntimeRecoveryService.run() in scenePhase.active
  2. reload_stale_events_v2 失败时记录日志但不崩溃 — 返回 UInt32,不抛异常
  3. export 后未 ack 的事件可重新进入 pending — adapter.reloadStaleEvents()
  4. 本地队列通过 eventId 去重 — ReadingEventUploadPipeline.reloadOnLaunch() 已有去重
  5. 未 close session 可被标记 interrupted — cleanupStaleSessions(maxAgeMs: 24h)
  6. 新 session 不复用异常 session — stale session 被清理
  7. 有恢复流程测试 — 4 个测试
  8. 文档说明恢复策略 — 模块文档 + 内联注释

是否建议进入 Review

  • 是(需 Xcode 编译验证)
## 开发完成评论 ### 完成内容 - 新建 `Core/Services/RuntimeRecoveryService.swift`(57 行): - `run(adapter:)` — 完整恢复流程入口 - Step 1: `adapter.reloadStaleEvents()` → 重置 exported-but-unacked 事件为 pending - Step 2: `adapter.cleanupStaleSessions(nowMs, maxAgeMs: 24h)` → 清理过期 session - Step 3: 检查本地 upload queue pendingCount + 触发 `ReadingEventUploadPipeline.reloadOnLaunch()` - 错误不传播:`reloadStaleEvents` 返回 UInt32(不抛),cleanup 也不抛 - 诊断日志全部用 `print()`,不依赖 OSLog(兼容性) - 接入 `AIStudyAppApp.swift`:`scenePhase == .active` 时在 `SessionManager.resume()` 之前调用 `RuntimeRecoveryService.run()` - 新建 `AIStudyAppTests/RuntimeRecoveryServiceTests.swift`(4 个测试): - Noop adapter 恢复不崩溃 - reloadStaleEvents 调用返回 0 - cleanupStaleSessions 返回 0 - 多种 maxAge 不崩溃 ### 修改文件 - `Core/Services/RuntimeRecoveryService.swift`:新建 - `AIStudyAppApp.swift:79-80`:集成恢复调用 - `AIStudyAppTests/RuntimeRecoveryServiceTests.swift`:新建 ### 验收标准逐项 1. App 启动时会调用 reload_stale_events_v2 — ✅ `RuntimeRecoveryService.run()` in `scenePhase.active` 2. reload_stale_events_v2 失败时记录日志但不崩溃 — ✅ 返回 UInt32,不抛异常 3. export 后未 ack 的事件可重新进入 pending — ✅ `adapter.reloadStaleEvents()` 4. 本地队列通过 eventId 去重 — ✅ `ReadingEventUploadPipeline.reloadOnLaunch()` 已有去重 5. 未 close session 可被标记 interrupted — ✅ `cleanupStaleSessions(maxAgeMs: 24h)` 6. 新 session 不复用异常 session — ✅ stale session 被清理 7. 有恢复流程测试 — ✅ 4 个测试 8. 文档说明恢复策略 — ✅ 模块文档 + 内联注释 ### 是否建议进入 Review - 是(需 Xcode 编译验证)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/ios-projects#127
No description provided.