docs: add stability remediation evidence
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
# 行情表格请求切换的订阅与缓冲清理
|
||||
|
||||
## 修复提交
|
||||
|
||||
| 仓库 | 分支 | Commit ID | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `harmony-ths-futures-apm-oom` | `work-20260820-clz-FUHM-1979-apm-oom` | `5f6e007c7f431521350a68543c4db5314a8c644f` | 修复 `TableRequestClient` 订阅泄漏 |
|
||||
| `harmony-ths-futures-apm-oom` | `work-20260820-clz-FUHM-1979-apm-oom` | `2ca4759c4e0212b4c80c41b94a0ae1339569e89c` | 完善请求切换时的统一清理逻辑 |
|
||||
|
||||
## 问题
|
||||
|
||||
行情列表通过 `TableRequestClient` 发起请求,并在带 `newrealtime=1` 的请求中向 `RealDataManager` 注册实时订阅。请求队列映射、实时订阅和请求缓冲分别以 instanceId、instanceId、`frameId + pageId + instanceId` 关联。
|
||||
|
||||
原有 `setPageId()` 先覆盖成员 `pageId`,再移除请求队列映射;随后无法获得旧 instanceId,也没有同步清理旧实时订阅与旧请求缓冲。切换行情分组时,`RealDataManager` 可能持续持有旧表格订阅并合并推送数据,造成 Local Heap 持续增长,最终有 OOM 风险。
|
||||
|
||||
## 优化方案
|
||||
|
||||
1. `setPageId()` 在覆盖成员前保存旧 `pageId`,递增请求生命周期版本并取消待执行通知,再统一解绑旧请求。
|
||||
2. 新增私有 `detachCurrentRequest(oldPageId)`:调用 `RequestQueueManagement.removeByClient(this)` 原子取得并移除旧 instanceId 映射;当旧 ID 有效时,用该 ID 移除 `RealDataManager` 订阅,并以旧 `pageId` 删除对应请求缓冲。
|
||||
3. `removeSubscriber()` 与 `release()` 复用同一解绑逻辑,避免多个生命周期出口清理策略不一致。
|
||||
4. 保留既有异步版本校验和 `setTimeout` 取消逻辑,防止过期解析结果或通知闭包继续持有表格数据。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅修改 `lib_baseUI` 的 `TableRequestClient` 请求切换和释放生命周期。
|
||||
- 不修改协议报文、请求参数、表格解析、实时数据合并或 UI 展示逻辑。
|
||||
- 请求未建立队列映射时,清理操作保持幂等并直接返回。
|
||||
|
||||
## 预期收益与边界
|
||||
|
||||
每次行情分组切换和页面销毁都会同时释放旧请求的队列映射、实时订阅及请求缓冲,避免孤儿订阅继续持有全量表格并接收实时推送,从而抑制 Local Heap 随切页线性增长。
|
||||
|
||||
该修改只治理 `TableRequestClient` 链路的引用滞留;图形内存、Web、Native/CPP 堆及其他业务模块的内存增长仍需按各自链路继续分析。`removeSubscriber` 与缓冲删除均按旧 instanceId 执行,因此不会误删新请求。
|
||||
|
||||
## 验证建议
|
||||
|
||||
1. 执行 `devecocli build --modules entry@default`,确认 ArkTS 编译和打包通过。
|
||||
2. 安装新 HAP 后,在“行情 → 国内”页的“全部主力 / 能源”等分组间连续切换 20~50 次,再离开列表页。
|
||||
3. 使用临时调试计数验证每次切换后旧订阅和旧请求缓冲各减少一项,待执行通知数归零;验证完成后不保留计数日志或缓冲快照统计代码。
|
||||
4. 检查应用崩溃日志,并结合 APM/HiLog 观察 Local Heap 在循环切换期间不再线性增长。
|
||||
|
||||
## 本次验证结果
|
||||
|
||||
在模拟器上完成 20 次同一活跃 `TableRequestClient` 的分组切换:每轮旧订阅数由 2 降至 1、请求缓冲数由 4 降至 3,待执行通知数始终为 0;离开行情列表后订阅数由 1 降至 0。未发现该应用的崩溃日志。上述计数日志仅用于本次验收,已从正式代码移除。
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# 行情表格实时通知合并
|
||||
|
||||
## 修复提交
|
||||
|
||||
| 仓库 | 分支 | Commit ID | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `harmony-ths-futures-apm-oom` | `work-20260820-clz-FUHM-1979-apm-oom` | `909ab7e83795ca418138da81dc8273c29046ce55` | 合并同一事件循环内的行情表格实时通知 |
|
||||
|
||||
## 问题
|
||||
|
||||
`TableRequestClient.notifyDataReceive()` 原先会为每条行情通知创建一个 `setTimeout` 回调,并将对应的 `TableData` 闭包保存在待执行回调中。实时推送频繁、UI 线程暂时繁忙时,同一轮事件循环内可能堆积多个回调,每个回调都引用一份完整表格数据,放大瞬时 Local Heap 占用。
|
||||
|
||||
## 优化方案
|
||||
|
||||
1. 将待通知状态改为单个定时器 ID 和最新一份 `TableData | string`。
|
||||
2. 新数据到达时仅覆盖待发送数据;已有定时器时不再创建新的回调。
|
||||
3. 定时器执行时读取并清空最新数据,再按既有请求生命周期版本校验后通知 UI。
|
||||
4. 请求切换和释放时取消该定时器并清空待通知数据,避免异步回调继续引用旧表格。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅修改 `lib_baseUI` 中 `TableRequestClient` 的通知调度方式。
|
||||
- 表格解析、实时订阅、请求参数和 UI 回调接口均不变。
|
||||
- 同一事件循环内 UI 只接收最新一份表格数据;中间的过期快照不再逐份分发。
|
||||
|
||||
## 预期收益与边界
|
||||
|
||||
待执行通知从“每条推送一个闭包”收敛为“一个定时器加一份最新数据”,降低高频行情期间短时间内同时存活的完整表格数量,并避免页面切换后遗留的待通知数据。
|
||||
|
||||
该优化会合并同一事件循环内的中间 UI 刷新,因此不保证逐条推送都触发一次 UI 回调;最终展示的数据保持为最新快照。网络接收、表格解析和实时订阅的吞吐不受本次修改限制。
|
||||
|
||||
## 验证结果
|
||||
|
||||
1. 使用现有依赖直接执行 Hvigor `assembleHap`,构建成功;子线程导入检查通过。
|
||||
2. 在 Pura 90 模拟器安装并启动应用,进入“行情 → 国内”,在“全部主力 / 能源”之间连续切换 20 轮,列表持续正常渲染。
|
||||
3. 离开行情页后查询应用崩溃日志,未发现 JS Crash 或 CppCrash。
|
||||
4. 同次验证临时记录了请求切换清理计数:每轮旧订阅由 2 降至 1、请求缓冲由 4 降至 3,且待通知数为 0;验证日志已删除,未纳入正式代码。
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# 交易 SDK 市价缓存去重与正确删除
|
||||
|
||||
## 修复提交
|
||||
|
||||
| 仓库 | 分支 | Commit ID | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `harmony_futures_trade_sdk` | `feature/FUHM-1979-match-push-optimization` | `fbfb85d704510fcbe94d1c58f732e0add4a76292` | 修复市价缓存重复写入和删除失效 |
|
||||
|
||||
## 问题
|
||||
|
||||
交易 SDK 的 `DefaultMarketPriceCacheCenter` 使用数组保存 `ContractMarketPrice`。原有 `removeCache()` 误用 `slice(i, 1)`,该调用只返回数组片段,不会修改原数组,因此已消费的缓存节点仍被单例缓存中心长期持有。
|
||||
|
||||
原有 `cachePrice()` 对相同 `ContractBean + MarketPriceType` 的价格也会无条件新增节点。交易页反复取价或刷新时,同一缓存键会持续产生重复对象;即使业务调用删除,旧节点仍无法从数组移除,导致缓存数组和 Local Heap 随调用次数增长。
|
||||
|
||||
## 优化方案
|
||||
|
||||
1. `removeCache()` 使用 `splice(i, 1)` 原地删除命中的缓存节点。
|
||||
2. `cachePrice()` 写入前查找相同合约和价格类型;命中时只更新最新价格并返回,不再追加重复节点。
|
||||
3. 验证阶段由主工程临时接入本地构建的 `FuTrade.har`,并在当前已安装依赖源码中同步修复,以支持不执行 `ohpm install` 的直接 Hvigor 构建;压力测试、测试入口和本地联调改动均不进入正式提交。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 生产代码修改位于兄弟仓库 `../harmony_futures_trade_sdk/FuTrade` 的市价缓存中心。
|
||||
- 主工程不修改交易协议、报价来源或下单流程;正式依赖仍保持 `1.0.13`,待交易 SDK 发布包含该修复的新版本后再升级版本号和锁定结果。
|
||||
- 缓存键仍为既有的 `ContractBean + MarketPriceType`;不同合约或价格类型之间不会互相覆盖。
|
||||
|
||||
## 预期收益与边界
|
||||
|
||||
相同缓存键连续更新时,缓存节点数量从随调用次数线性增长收敛为最多一个;缓存被消费后会立即从数组移除。以本次 100 万次重复更新为例,节点数由潜在的 100 万个降至 1 个,删除后降至 0,重复节点消除率接近 100%。
|
||||
|
||||
根据 `ContractMarketPrice` 的对象字段、数组引用和价格字符串开销保守估算,100 万次重复更新可避免约 60~120 MB 的常驻堆增长;20 万次缓存/删除循环可避免约 12~24 MB 的残留。实际 PSS 和 JS Heap 收益受 Ark Runtime 对象布局、字符串表示及 GC 时机影响,仍应以修复前后的同场景 Profiler Trace 为准。
|
||||
|
||||
该修改只治理交易 SDK 市价缓存数组的重复节点和错误删除,不覆盖全局行情请求缓冲、实时订阅、整表解析与 UI 全量刷新等其他 OOM 链路。
|
||||
|
||||
## 验证结果
|
||||
|
||||
1. `FuTrade@default` 构建成功并生成新的 `FuTrade.har`;主工程 `entry@default` 和 `entry@ohosTest` 均通过直接 Hvigor 构建。
|
||||
2. 在 Pura 90 模拟器执行 100 万次相同缓存键更新,耗时 148 ms;最终读取到最新价格,删除后缓存为空。
|
||||
3. 在同一测试进程执行 20 万次缓存/删除循环,耗时 90 ms;循环结束后缓存为空。
|
||||
4. Instrumented Test 共执行 4 个用例,结果为 `Pass 4 / Failure 0 / Error 0`,总测试耗时 239 ms。
|
||||
5. 测试完成后未发现 `com.hexin.plat.hmn.futures` 的 OOM、JS Crash 或 CppCrash 记录。
|
||||
6. 提交前已清理主工程的本地 HAR 路径、`oh_modules` 同步修改、临时 Instrumented Test 接入和 SDK 测试代码,避免提交不可移植的联调依赖或压力测试代码。
|
||||
@@ -0,0 +1,37 @@
|
||||
# WebView 行情总览销毁时释放请求 Client
|
||||
|
||||
## 修复提交
|
||||
|
||||
| 仓库 | 分支 | Commit ID | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `harmony-ths-futures-apm-oom` | `work-20260820-clz-FUHM-1979-apm-oom` | `da5599387d82f06256e9224eb8c8001410de1a6b` | 补齐总览页面销毁解绑并断开 bridge 引用 |
|
||||
|
||||
## 问题
|
||||
|
||||
`OverViewEvent` 只处理 WebView 前后台切换,没有注册页面销毁回调。三个行情 client 已登记到全局 `RequestQueueManagement` 并写入请求缓冲后,如果 WebView 直接销毁,全局队列仍可能持有 client,client 又反向持有 `WebViewJavaScriptBridge`,形成跨页面 retained reference。
|
||||
|
||||
## 优化方案
|
||||
|
||||
1. 注册 `addAboutToDisappearCallBack()`,页面销毁时统一调用 `stopRequest()`。
|
||||
2. `stopRequest()` 在取消三个请求后清空每个 client 的 bridge 引用和 `OverViewEvent` 成员引用,使重复调用保持幂等。
|
||||
3. 销毁回调最后清空 `OverViewEvent.webViewJSBridge`,切断事件对象到 WebView bridge 的引用。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅修改行情总览 JS 事件的销毁生命周期。
|
||||
- 不修改三个行情协议的 frameId、pageId、请求文本、解析或回调数据结构。
|
||||
- 前后台恢复行为保持不变;已收到 JS 事件时仍会在回到前台后重新创建请求。
|
||||
|
||||
## 预期收益与边界
|
||||
|
||||
每次总览页销毁会确定释放 3 个请求 client、对应请求缓冲和队列映射,避免其随反复进出页面线性累积。实际节省内存取决于 bridge、请求参数和页面对象引用图,本次没有设备堆快照,因此不估算具体 MB 或百分比。
|
||||
|
||||
该修复只覆盖 `OverViewEvent` 的三条行情请求,不代表其他 WebView JS 事件或全局行情链路已全部完成生命周期审计。
|
||||
|
||||
## 验证结果
|
||||
|
||||
1. 对实际 `OverViewEvent.ets` 转译后使用模拟 bridge、请求缓冲和队列执行 20,000 轮“注册 → 发起 3 个请求 → 页面销毁”;每轮销毁后 retained request 为 0、retained client 为 0。
|
||||
2. 使用直接 Hvigor 构建 `entry@default` Debug HAP 成功,完整 ArkTS 编译、HAP 打包和签名通过;未执行安装。
|
||||
3. 临时压力测试文件和日志已删除,生产代码未保留计数、测试入口或调试日志。
|
||||
4. 确认“行情 → 总览”页面可加载;主 Tab 切换只改变前后台状态,不销毁 `OverViewPage`,当前产品入口无法在不终止 Ability 的情况下重复触发本修复的页面销毁回调,因此未将主 Tab 循环冒充为 `OverViewEvent` 销毁验证。
|
||||
5. 同次设备测试检查应用 Crash 日志及近 20 分钟错误日志,未发现 OOM、JS Crash、CppCrash、SIGSEGV 或 SIGABRT。
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# 通信 SDK 跨 Frame 请求缓冲清理与 HAR 接入
|
||||
|
||||
## 修复提交
|
||||
|
||||
| 仓库 | 分支 | Commit ID | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `ohos_mobile_lib_communication` | `work-freeze-lib-communication-1.1.1` | `4784e4698a3d335471f275fd7c8dd9c113323b78` | 清理旧 frame 请求缓冲及关联客户端 |
|
||||
| `harmony-ths-futures-apm-oom` | `work-20260820-clz-FUHM-1979-apm-oom` | `da5599387d82f06256e9224eb8c8001410de1a6b` | 替换主工程本地通信 HAR |
|
||||
|
||||
## 问题
|
||||
|
||||
通信 SDK 在 `frameId` 变化时调用的 `clearRequestPageList()` 为空实现,导致旧 `_sReqList`、`_sTempReqBuff`、`_sTemqReqList` 和队列 client 引用继续保留。重复请求还可能通过 instance 映射形成伴随 client,仅删除主 instance 不能完整释放引用链。
|
||||
|
||||
## 优化方案
|
||||
|
||||
1. SDK 在切换 frame 时收集旧缓冲的全部 instanceId,释放主 instance 及其伴随 instance。
|
||||
2. 清空三个缓冲并重置当前 frame,再按原流程写入新 frame。
|
||||
3. 保留切换前已登记的新 client,不使用破坏范围更大的全局队列清空。
|
||||
4. 构建新的 `lib_communication.har` 并替换主工程 `libs/lib_communication20250903.har`;依赖版本仍为 `1.1.1-beta.1`,lockfile 不变。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- SDK 修改位于 `../ohos_mobile_lib_communication/lib_communication`。
|
||||
- 主工程只替换本地通信 HAR,不修改调用 API 或请求协议。
|
||||
- `HQ2RequestManager`、实时行情整表复制和 UI 全量解析不在本次范围。
|
||||
|
||||
## 预期收益与边界
|
||||
|
||||
跨 frame 切换后,请求缓冲由历史 frame 累积收敛为仅保留当前 frame;旧请求文本和 client 引用不再随切页次数线性增长。压力测试 20,000 轮后缓冲仍为 1 项,说明该场景的历史缓冲残留消除率为 100%。
|
||||
|
||||
上述结论只针对测试中的缓冲项和 client 计数,不等同于整进程 PSS 降幅;图片、Web、Native 堆及行情整表分配仍可能影响 OOM。
|
||||
|
||||
## 验证结果
|
||||
|
||||
1. SDK 实际源码压力测试执行 20,000 轮,每轮创建两个相同请求 client 并切换 frame;最终请求缓冲为 1、当前活跃 client 为 2,旧主/伴随 client 均为 0。
|
||||
2. 直接 Hvigor 构建 SDK Release HAR 成功,耗时 9.145 秒;接入产物 SHA-256 为 `b6919758c15761d5f9f75dcc0bf608d6ab2b46bd5ada9c92ddf1237be8b39621`。
|
||||
3. 主工程直接 Hvigor 构建 `entry@default` Debug HAP 成功,耗时 34.940 秒;未执行 `ohpm install`、安装或启动。
|
||||
4. 临时 API 24 配置、压力测试代码和日志均已清理;因本机缺少 API 20 SDK,API 20 构建与设备 OOM/PSS 验证未执行。
|
||||
5. 2026-08-21 在 Pura 90 API 24 模拟器执行有效的首页、行情、交易、发现四 Tab 定向循环 20 轮,每次切换等待 1 秒;进程持续存活,Crash 日志中未发现 OOM、JS Crash、CppCrash、SIGSEGV 或 SIGABRT。
|
||||
6. 设备有效循环前 Total PSS / ArkTS heap PSS / Native heap PSS 分别为 562,024 / 110,843 / 211,017 kB;20 轮后空闲 15 秒分别为 679,879 / 199,328 / 244,622 kB。整进程内存未回到基线,且生产包没有请求缓冲计数能力,因此该设备结果不能单独证明跨 frame 缓冲已释放,也不能据此将剩余增长归因于本链路;缓冲收敛结论仍以 20,000 轮白盒计数测试为依据。
|
||||
7. 首次尝试因首页 VIP 广告遮挡底部 Tab,点击未实际切页,该组数据已作废且未计入上述 20 轮结果。
|
||||
@@ -0,0 +1,43 @@
|
||||
# 广告悬浮球 PixelMap 与 Dialog ID 生命周期清理
|
||||
|
||||
## 修复提交
|
||||
|
||||
| 仓库 | 分支 | Commit ID | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `harmony-ths-futures-apm-oom` | `work-20260820-clz-FUHM-1979-apm-oom` | `da5599387d82f06256e9224eb8c8001410de1a6b` | 释放广告 PixelMap、失效异步创建并清空 ID |
|
||||
|
||||
## 问题
|
||||
|
||||
`AdsBallManager` 创建完整图和半图 PixelMap 后未保存并显式释放所有权。任一图片加载失败时,另一张已成功创建的 PixelMap 会直接遗漏;弹窗销毁也只 dismiss dialog,不释放 Native 图片资源。`floatBallIds` 使用只增不删的数组,主题切换和广告刷新会长期保留历史 ID。
|
||||
|
||||
异步图片加载期间如果发生新的创建或销毁,旧任务还可能在完成后覆盖新弹窗,使旧图片和 dialog 生命周期错配。
|
||||
|
||||
## 优化方案
|
||||
|
||||
1. manager 明确持有当前完整图和半图 PixelMap,并在 dialog dismiss 后延迟 300 ms 释放,避免关闭动画仍使用图片。
|
||||
2. 单图失败、弹窗 show 失败和异步任务过期时立即释放已创建但未展示的 PixelMap。
|
||||
3. 使用 generation 标记使旧异步创建失效,避免旧结果覆盖新弹窗。
|
||||
4. `floatBallIds` 改为 `Set<string>`,销毁后清空;兜底 dismiss 仍按历史登记 ID 匹配当前页面 dialog。
|
||||
5. 对同一 PixelMap 被同时作为完整图和半图的异常情况只调用一次 `release()`。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅修改首页广告悬浮球的图片与 dialog 生命周期管理。
|
||||
- 不修改广告数据、图片地址、展示样式、点击行为或 300 ms 创建防抖。
|
||||
- 延迟释放只持有已 dismiss 的当前两张图片 300 ms,避免立即释放与弹窗关闭动画竞争。
|
||||
|
||||
## 预期收益与边界
|
||||
|
||||
成功展示的每个悬浮球最多持有当前 2 张 PixelMap,销毁后释放;单图失败不再残留另一张;ID 集合在销毁后归零。Native 图片内存收益取决于图片尺寸、像素格式和系统解码缓存,本次未取得真实图片尺寸与设备 PSS,因此不估算具体 MB。
|
||||
|
||||
该修复不治理其他广告弹窗、网络图片缓存、系统图形缓存或 GPU/DMA 内存,也不代表 Native PSS 会在 release 后立即同步下降。
|
||||
|
||||
## 验证结果
|
||||
|
||||
1. 对实际 `AdsBallManager.ets` 转译后执行 5,000 轮成功创建/销毁、5,000 轮半图失败和 1 轮异步过期压力测试。
|
||||
2. 共创建 15,001 个模拟 PixelMap,全部恰好释放一次;最终 retained dialog ID 为 0、retained dialog 为 0。
|
||||
3. 新实现随主工程通过直接 Hvigor `entry@default` Debug HAP 构建,完整 ArkTS 编译、打包和签名成功;未执行安装。
|
||||
4. 压力测试中将 300 ms 延迟计时器同步触发以校验所有权计数;真实设备关闭动画时序、Native PSS 回落和 OOM 日志未验证。
|
||||
5. 临时压力测试代码与日志已删除,未在生产代码中保留调试计数。
|
||||
6. 2026-08-21 在 Pura 90 API 24 模拟器确认首页 VIP 广告真实展示并成功关闭,关闭后底部 Tab 恢复可操作;同次测试未发现 OOM、JS Crash、CppCrash、SIGSEGV 或 SIGABRT。
|
||||
7. 本次设备操作只完成 1 次真实广告展示/关闭,未取得 PixelMap Native 归属计数,也未完成 50~100 轮重复广告创建,因此不宣称真实设备 Native PSS 稳态已经验证;重复释放正确性仍以 15,001 个模拟 PixelMap 的白盒压力测试为依据。
|
||||
Reference in New Issue
Block a user